vybekiit 0.7.26 → 0.7.27

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 (232) hide show
  1. package/dist/bin.js +3891 -1301
  2. package/dist/global-skills/aws-cdk/SKILL.md +19 -5
  3. package/dist/global-skills/aws-cdk/references/fast-deployments.md +191 -0
  4. package/dist/global-skills/aws-cdk/references/troubleshooting-deployment.md +16 -0
  5. package/dist/global-skills/aws-cloudformation/SKILL.md +16 -26
  6. package/dist/global-skills/aws-cloudformation/references/check-cloudformation-template-compliance.script.md +7 -3
  7. package/dist/global-skills/aws-cloudformation/references/cloudformation-language-server.md +177 -0
  8. package/dist/global-skills/aws-cloudformation/references/cloudformation-pre-deploy-validation.script.md +8 -2
  9. package/dist/global-skills/aws-cloudformation/references/persist-template-context.script.md +5 -8
  10. package/dist/global-skills/aws-cloudformation/references/retrieve-template-context.script.md +1 -1
  11. package/dist/global-skills/aws-cloudformation/references/security-considerations.md +51 -0
  12. package/dist/global-skills/aws-cloudformation/references/troubleshoot-failed-stack.script.md +138 -0
  13. package/dist/global-skills/aws-cloudformation/references/{validate-cloudformation-template.script.md → validate-with-cfn-lint.script.md} +15 -27
  14. package/dist/global-skills/aws-cloudformation/references/validate-with-cloudformation-validate.script.md +181 -0
  15. package/dist/global-skills/aws-cloudformation/references/validation-tool-selection.md +44 -0
  16. package/dist/global-skills/aws-serverless/SKILL.md +9 -1
  17. package/dist/global-skills/aws-serverless/references/architecture.md +3 -1
  18. package/dist/global-skills/aws-serverless/references/lambda.md +3 -1
  19. package/dist/global-skills/aws-serverless/references/orchestration.md +1 -0
  20. package/dist/global-skills/better-auth-best-practices/SKILL.md +18 -8
  21. package/dist/global-skills/eas-app-stores/SKILL.md +31 -15
  22. package/dist/global-skills/eas-app-stores/agents/openai.yaml +2 -2
  23. package/dist/global-skills/eas-app-stores/references/ios-app-store.md +37 -32
  24. package/dist/global-skills/eas-app-stores/references/native-ios.md +167 -0
  25. package/dist/global-skills/eas-app-stores/references/play-store.md +3 -7
  26. package/dist/global-skills/eas-app-stores/references/testflight.md +39 -35
  27. package/dist/global-skills/eas-simulator/SKILL.md +48 -26
  28. package/dist/global-skills/eas-simulator/references/controllers.md +32 -3
  29. package/dist/global-skills/eas-simulator/references/run-your-app.md +34 -4
  30. package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -4
  31. package/dist/global-skills/eas-update/SKILL.md +146 -0
  32. package/dist/global-skills/eas-update/agents/openai.yaml +4 -0
  33. package/dist/global-skills/expo-animation/RECIPES.md +2 -2
  34. package/dist/global-skills/expo-animation/SKILL.md +9 -2
  35. package/dist/global-skills/expo-brownfield/SKILL.md +18 -11
  36. package/dist/global-skills/expo-brownfield/agents/openai.yaml +2 -2
  37. package/dist/global-skills/expo-brownfield/references/brownfield-integrated.md +94 -69
  38. package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +40 -42
  39. package/dist/global-skills/expo-brownfield/references/comparison.md +5 -5
  40. package/dist/global-skills/expo-brownfield/references/feature-integration.md +163 -0
  41. package/dist/global-skills/expo-brownfield/references/troubleshooting.md +17 -17
  42. package/dist/global-skills/expo-brownfield/references/version-compatibility.md +40 -0
  43. package/dist/global-skills/expo-data-fetching/SKILL.md +27 -6
  44. package/dist/global-skills/expo-design-system/SKILL.md +27 -7
  45. package/dist/global-skills/expo-design-system/references/audit.md +7 -2
  46. package/dist/global-skills/expo-design-system/references/native-slop.md +74 -0
  47. package/dist/global-skills/expo-examples/SKILL.md +0 -1
  48. package/dist/global-skills/expo-examples/references/catalog.md +1 -1
  49. package/dist/global-skills/expo-migrate-module/SKILL.md +21 -10
  50. package/dist/global-skills/expo-migrate-module/references/compatibility.md +80 -23
  51. package/dist/global-skills/expo-migrate-module/references/migration-map.md +162 -11
  52. package/dist/global-skills/expo-native-ui/SKILL.md +25 -16
  53. package/dist/global-skills/expo-native-ui/agents/openai.yaml +2 -2
  54. package/dist/global-skills/expo-native-ui/references/controls.md +5 -46
  55. package/dist/global-skills/expo-native-ui/references/icons.md +21 -2
  56. package/dist/global-skills/expo-native-ui/references/media.md +15 -20
  57. package/dist/global-skills/expo-native-ui/references/visual-effects.md +12 -11
  58. package/dist/global-skills/expo-overview/SKILL.md +17 -12
  59. package/dist/global-skills/expo-router/SKILL.md +5 -3
  60. package/dist/global-skills/expo-router/references/tabs.md +5 -5
  61. package/dist/global-skills/expo-upgrade/SKILL.md +3 -1
  62. package/dist/global-skills/expo-web-to-native/references/false-friends.md +2 -2
  63. package/dist/global-skills/expo-web-to-native/references/native-patterns.md +1 -1
  64. package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +13 -16
  65. package/dist/global-skills/firebase-ai-logic-basics/references/ios_setup.md +4 -5
  66. package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_android.md +4 -4
  67. package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_web.md +3 -3
  68. package/dist/global-skills/firebase-auth-basics/SKILL.md +11 -6
  69. package/dist/global-skills/firebase-auth-basics/references/client_sdk_android.md +4 -5
  70. package/dist/global-skills/firebase-auth-basics/references/client_sdk_web.md +3 -3
  71. package/dist/global-skills/firebase-auth-basics/references/flutter_setup.md +24 -25
  72. package/dist/global-skills/firebase-auth-basics/references/security_rules.md +4 -2
  73. package/dist/global-skills/firebase-crashlytics/references/android_setup.md +7 -4
  74. package/dist/global-skills/firebase-crashlytics/references/ios_setup.md +2 -3
  75. package/dist/global-skills/firebase-data-connect/SKILL.md +2 -1
  76. package/dist/global-skills/firebase-data-connect/examples.md +4 -4
  77. package/dist/global-skills/firebase-data-connect/reference/config.md +5 -4
  78. package/dist/global-skills/firebase-data-connect/reference/realtime.md +1 -2
  79. package/dist/global-skills/firebase-data-connect/reference/sdk_flutter.md +2 -2
  80. package/dist/global-skills/firebase-data-connect/reference/sdk_ios.md +2 -2
  81. package/dist/global-skills/firebase-data-connect/reference/sdk_web.md +17 -6
  82. package/dist/global-skills/firebase-data-connect/reference/security.md +5 -5
  83. package/dist/global-skills/firebase-data-connect/templates.md +2 -1
  84. package/dist/global-skills/firebase-firestore/SKILL.md +20 -8
  85. package/dist/global-skills/firebase-firestore/references/enterprise/android_sdk_usage.md +5 -4
  86. package/dist/global-skills/firebase-firestore/references/enterprise/data_model.md +12 -3
  87. package/dist/global-skills/firebase-firestore/references/enterprise/indexes.md +16 -18
  88. package/dist/global-skills/firebase-firestore/references/enterprise/provisioning.md +1 -1
  89. package/dist/global-skills/firebase-firestore/references/enterprise/python_sdk_usage.md +5 -1
  90. package/dist/global-skills/firebase-firestore/references/enterprise/web_sdk_usage.md +7 -7
  91. package/dist/global-skills/firebase-firestore/references/standard/android_sdk_usage.md +5 -5
  92. package/dist/global-skills/firebase-firestore/references/standard/flutter_setup.md +4 -4
  93. package/dist/global-skills/firebase-firestore/references/standard/indexes.md +16 -18
  94. package/dist/global-skills/firebase-firestore/references/standard/provisioning.md +1 -1
  95. package/dist/global-skills/firebase-remote-config-basics/SKILL.md +0 -5
  96. package/dist/global-skills/firebase-remote-config-basics/references/android_setup.md +36 -8
  97. package/dist/global-skills/firebase-remote-config-basics/references/ios_setup.md +1 -7
  98. package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +17 -6
  99. package/dist/global-skills/{firebase-firestore/references/standard/security_rules.md → firestore-rules-creation/SKILL.md} +24 -13
  100. package/dist/global-skills/grow-my-customers/SKILL.md +23 -0
  101. package/dist/global-skills/instrument-feature-flags/SKILL.md +25 -25
  102. package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +141 -285
  103. package/dist/global-skills/instrument-feature-flags/references/android.md +6 -15
  104. package/dist/global-skills/instrument-feature-flags/references/api.md +4 -11
  105. package/dist/global-skills/instrument-feature-flags/references/best-practices.md +1 -13
  106. package/dist/global-skills/instrument-feature-flags/references/django.md +14 -27
  107. package/dist/global-skills/instrument-feature-flags/references/dotnet.md +20 -79
  108. package/dist/global-skills/instrument-feature-flags/references/elixir.md +1 -9
  109. package/dist/global-skills/instrument-feature-flags/references/flask.md +13 -13
  110. package/dist/global-skills/instrument-feature-flags/references/flutter.md +3 -24
  111. package/dist/global-skills/instrument-feature-flags/references/go.md +3 -15
  112. package/dist/global-skills/instrument-feature-flags/references/ios.md +4 -17
  113. package/dist/global-skills/instrument-feature-flags/references/java.md +5 -13
  114. package/dist/global-skills/instrument-feature-flags/references/laravel.md +13 -17
  115. package/dist/global-skills/instrument-feature-flags/references/next-js.md +25 -32
  116. package/dist/global-skills/instrument-feature-flags/references/nodejs.md +8 -15
  117. package/dist/global-skills/instrument-feature-flags/references/php.md +1 -15
  118. package/dist/global-skills/instrument-feature-flags/references/python.md +2 -15
  119. package/dist/global-skills/instrument-feature-flags/references/react-native.md +13 -15
  120. package/dist/global-skills/instrument-feature-flags/references/react.md +17 -21
  121. package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +37 -83
  122. package/dist/global-skills/instrument-feature-flags/references/ruby.md +2 -15
  123. package/dist/global-skills/instrument-feature-flags/references/rust.md +13 -25
  124. package/dist/global-skills/instrument-feature-flags/references/usage.md +14 -63
  125. package/dist/global-skills/instrument-feature-flags/references/web.md +9 -14
  126. package/dist/global-skills/instrument-product-analytics/SKILL.md +29 -29
  127. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +3 -1
  128. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +3 -1
  129. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-static.md +3 -1
  130. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-view-transitions.md +3 -1
  131. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +3 -1
  132. package/dist/global-skills/instrument-product-analytics/references/android.md +72 -107
  133. package/dist/global-skills/instrument-product-analytics/references/angular.md +26 -28
  134. package/dist/global-skills/instrument-product-analytics/references/astro.md +13 -24
  135. package/dist/global-skills/instrument-product-analytics/references/configuration.md +45 -63
  136. package/dist/global-skills/instrument-product-analytics/references/django.md +14 -27
  137. package/dist/global-skills/instrument-product-analytics/references/dotnet.md +20 -79
  138. package/dist/global-skills/instrument-product-analytics/references/elixir.md +47 -49
  139. package/dist/global-skills/instrument-product-analytics/references/flask.md +13 -13
  140. package/dist/global-skills/instrument-product-analytics/references/flutter.md +60 -90
  141. package/dist/global-skills/instrument-product-analytics/references/go.md +17 -56
  142. package/dist/global-skills/instrument-product-analytics/references/identify-users.md +15 -15
  143. package/dist/global-skills/instrument-product-analytics/references/ios.md +11 -15
  144. package/dist/global-skills/instrument-product-analytics/references/laravel.md +13 -17
  145. package/dist/global-skills/instrument-product-analytics/references/next-js.md +25 -32
  146. package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +13 -27
  147. package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +14 -28
  148. package/dist/global-skills/instrument-product-analytics/references/php.md +33 -84
  149. package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +229 -9
  150. package/dist/global-skills/instrument-product-analytics/references/python.md +415 -106
  151. package/dist/global-skills/instrument-product-analytics/references/react-native.md +161 -155
  152. package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +12 -33
  153. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +15 -33
  154. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +12 -33
  155. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +26 -41
  156. package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +37 -83
  157. package/dist/global-skills/instrument-product-analytics/references/ruby.md +48 -108
  158. package/dist/global-skills/instrument-product-analytics/references/svelte.md +18 -24
  159. package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +17 -19
  160. package/dist/global-skills/instrument-product-analytics/references/usage.md +14 -63
  161. package/dist/global-skills/instrument-product-analytics/references/vue-js.md +29 -28
  162. package/dist/global-skills/manifest.json +8 -2
  163. package/dist/global-skills/mongodb-search-and-ai/SKILL.md +28 -37
  164. package/dist/global-skills/mongodb-search-and-ai/references/automated-embedding.md +438 -0
  165. package/dist/global-skills/mongodb-search-and-ai/references/hybrid-search.md +60 -4
  166. package/dist/global-skills/mongodb-search-and-ai/references/vector-search.md +46 -108
  167. package/dist/global-skills/neon/SKILL.md +207 -213
  168. package/dist/global-skills/neon/references/auth.md +12 -0
  169. package/dist/global-skills/neon/references/claimable-neon.md +10 -14
  170. package/dist/global-skills/neon/references/function-triggers.md +53 -0
  171. package/dist/global-skills/neon/references/logs-loki.md +61 -0
  172. package/dist/global-skills/neon/references/parse-env.md +32 -0
  173. package/dist/global-skills/neon/references/sdk.md +7 -0
  174. package/dist/global-skills/neon-ai-gateway/SKILL.md +14 -16
  175. package/dist/global-skills/neon-auth/SKILL.md +155 -0
  176. package/dist/global-skills/neon-auth/references/managed-auth.md +173 -0
  177. package/dist/global-skills/neon-auth/references/self-managed.md +25 -0
  178. package/dist/global-skills/neon-functions/SKILL.md +159 -84
  179. package/dist/global-skills/neon-functions/references/ai-sdk.md +4 -6
  180. package/dist/global-skills/neon-functions/references/function-triggers.md +249 -0
  181. package/dist/global-skills/neon-functions/references/mastra-studio.md +3 -3
  182. package/dist/global-skills/neon-functions/references/mcp.md +1 -1
  183. package/dist/global-skills/neon-functions/references/production-hardening.md +340 -0
  184. package/dist/global-skills/neon-functions/references/sse.md +8 -5
  185. package/dist/global-skills/neon-object-storage/SKILL.md +10 -11
  186. package/dist/global-skills/neon-postgres/SKILL.md +120 -17
  187. package/dist/global-skills/neon-postgres/references/full-text-search.md +99 -0
  188. package/dist/global-skills/neon-postgres/references/hybrid-search.md +90 -0
  189. package/dist/global-skills/neon-postgres/references/lakebase-search-drizzle.md +172 -0
  190. package/dist/global-skills/neon-postgres/references/vector-search.md +137 -0
  191. package/dist/global-skills/neon-postgres-branches/SKILL.md +3 -3
  192. package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +1 -1
  193. package/dist/global-skills/onboarding/SKILL.md +8 -6
  194. package/dist/global-skills/resend/SKILL.md +4 -2
  195. package/dist/global-skills/resend/references/broadcasts.md +6 -1
  196. package/dist/global-skills/resend/references/receiving.md +29 -10
  197. package/dist/global-skills/resend/references/sending/email-management.md +14 -4
  198. package/dist/global-skills/resend/references/topics.md +9 -6
  199. package/dist/global-skills/resend/references/usage.md +117 -0
  200. package/dist/global-skills/resend/references/webhooks.md +59 -2
  201. package/dist/global-skills/stripe-best-practices/SKILL.md +35 -29
  202. package/dist/global-skills/stripe-best-practices/references/billing.md +9 -2
  203. package/dist/global-skills/stripe-best-practices/references/payments.md +4 -2
  204. package/dist/global-skills/stripe-best-practices/references/security.md +3 -1
  205. package/dist/global-skills/stripe-best-practices/references/tax.md +39 -20
  206. package/dist/global-skills/supabase/SKILL.md +6 -0
  207. package/dist/global-skills/use-railway/SKILL.md +42 -22
  208. package/dist/global-skills/use-railway/references/analyze-db.md +7 -6
  209. package/dist/global-skills/use-railway/references/cloud-agents.md +70 -0
  210. package/dist/global-skills/use-railway/references/configure.md +17 -2
  211. package/dist/global-skills/use-railway/references/databases.md +107 -0
  212. package/dist/global-skills/use-railway/references/deploy.md +5 -5
  213. package/dist/global-skills/use-railway/references/feature-flags.md +25 -13
  214. package/dist/global-skills/use-railway/references/iac.md +66 -77
  215. package/dist/global-skills/use-railway/references/operate.md +26 -3
  216. package/dist/global-skills/use-railway/references/request.md +31 -23
  217. package/dist/global-skills/use-railway/references/setup.md +16 -5
  218. package/dist/global-skills/use-railway/references/tracing.md +261 -0
  219. package/dist/global-skills/use-railway/references/usage.md +52 -0
  220. package/dist/global-skills/validate-my-idea/SKILL.md +54 -0
  221. package/dist/global-skills/{feedback → vybekiit-feedback}/SKILL.md +16 -12
  222. package/dist/global-skills/watch-my-app/SKILL.md +53 -0
  223. package/dist/global-skills/workers-best-practices/SKILL.md +36 -103
  224. package/dist/global-skills/workers-best-practices/references/configuration.md +139 -0
  225. package/dist/global-skills/workers-best-practices/references/platform-apis.md +51 -0
  226. package/dist/global-skills/workers-best-practices/references/{rules.md → runtime-patterns.md} +13 -137
  227. package/dist/global-skills/wrangler/SKILL.md +48 -901
  228. package/dist/global-skills/xcode-project-setup/scripts/xcode_spm_setup/Sources/main.swift +19 -15
  229. package/package.json +9 -8
  230. package/dist/global-skills/expo-native-ui/references/animations.md +0 -220
  231. package/dist/global-skills/firebase-firestore/references/enterprise/security_rules.md +0 -577
  232. package/dist/global-skills/workers-best-practices/references/review.md +0 -174
@@ -19,7 +19,7 @@ public final class CameraModule: Module {
19
19
  public final class CameraModule: Module {}
20
20
  ```
21
21
 
22
- Always carry a custom 1.0 name into `@ExpoModule("...")`. A stale `Name(...)` can override or conflict with the 2.0 name depending on the core revision. In mixed mode, remove `Name(...)` only after verifying the installed `_jsName` contract.
22
+ Always carry a custom 1.0 name into `@ExpoModule("...")`. A stale `Name(...)` can override or conflict with the 2.0 name depending on the core revision. In mixed mode, remove `Name(...)` only after verifying that the module still resolves under its expected JS name.
23
23
 
24
24
  ## Functions
25
25
 
@@ -108,9 +108,56 @@ func download(url: URL) -> Task<DownloadResult, any Error> {
108
108
 
109
109
  This shape requires the `JavaScriptEncodable` conformance for `Task` in the checked-out core (encode-only; a JS promise does not decode back into a `Task`). Verify it exists before using this shape; prefer shape 1 or 2 when it is absent.
110
110
 
111
- Do not assume scheduling is unchanged. 2.0 async members are `@JavaScriptActor`-isolated and begin on the JS thread until their first real suspension. A 1.0 `.runOnQueue(...)` function or a workload that relied on automatic background execution must not be migrated as-is. Never move blocking I/O directly onto the JS actor.
111
+ #### Threading: the architectural difference
112
112
 
113
- For a queue-pinned 1.0 function, prefer restructuring the work onto the Swift Concurrency model (structured concurrency, an actor, or a detached task for blocking work). When that is not feasible because the queue itself is the contract, for example a library that must be called from one serial queue, convert to an `async` method that dispatches to that queue inside a checked continuation:
113
+ This is the highest-risk part of an async migration, because the code compiles and the JS signature is unchanged while the work moves onto a different thread.
114
+
115
+ The two versions schedule async work from opposite starting points:
116
+
117
+ | | 1.0 `AsyncFunction` | 2.0 `async` `@JS` |
118
+ | --- | --- | --- |
119
+ | Where the body starts | off the JS thread, from the first statement | on the JS thread |
120
+ | When it leaves the JS thread | never runs there | at the first real suspension point |
121
+ | A body with no `await` | still runs off the JS thread | runs entirely on the JS thread |
122
+
123
+ A 2.0 async member is `@JavaScriptActor`-isolated and stays on the JS thread until it actually suspends. So the dangerous case is a function marked `async` whose body never awaits anything, or awaits only after doing substantial work. In 1.0 that body was off the JS thread from the start; migrated verbatim to 2.0, it now blocks the JS thread for its full duration. Nothing in the JS contract reveals this: the function still returns a promise.
124
+
125
+ Audit every `AsyncFunction` you migrate for what the body does before its first `await`:
126
+
127
+ - No `await` at all, or synchronous work ahead of the first `await`: the work is on the JS thread. Treat it as a regression unless the body is trivial.
128
+ - Blocking I/O, file or database access, image or crypto work, or an unbounded loop: never leave this on the JS actor.
129
+ - `.runOnQueue(...)` in 1.0: the queue was the contract. See the continuation pattern below.
130
+
131
+ To restore the 1.0 behavior, use the `@JS` macro's `.concurrent` option (macros plugin `0.10.0`):
132
+
133
+ ```swift
134
+ // Body runs off the JS thread, like a 1.0 AsyncFunction.
135
+ @JS(.concurrent)
136
+ func process(input: String) async throws -> String {
137
+ return try self.processor.process(input)
138
+ }
139
+
140
+ // The option is variadic, so it combines with an explicit JS name.
141
+ @JS("doWork", .concurrent)
142
+ func performWork() async throws {}
143
+ ```
144
+
145
+ Only the body moves. Arguments are still decoded on the JS thread and the result is still encoded back on it, so `.concurrent` changes execution, not the JS contract. This makes it the preferred fix for a migrated function that relied on 1.0 background execution: the intent is explicit and the body stays as written.
146
+
147
+ Two constraints:
148
+
149
+ - `.concurrent` is valid only on an `async` function. On anything else it is diagnosed on the `@JS` attribute.
150
+ - You must still write `async` yourself. The macro cannot add it, but the diagnostic for a synchronous function carries a fix-it that inserts it.
151
+
152
+ **Check availability first.** The plugin side shipped in `0.10.0`, but this also needs a `JSOptions` type and a second `@JS` overload in core, because the options-only spelling cannot reuse the unlabeled JS-name slot. Core has lagged the plugin on every 2.0 feature so far, and where it still declares `@JS(_ jsName: String? = nil)` alone, `@JS(.concurrent)` does not compile. Read the checked-out declaration:
153
+
154
+ ```bash
155
+ grep -rn 'public macro JS' <expo-modules-core>
156
+ ```
157
+
158
+ If it takes only a name, use the explicit patterns below instead.
159
+
160
+ For a queue-pinned function, or when `.concurrent` is unavailable, prefer restructuring the work onto the Swift Concurrency model (structured concurrency, an actor, or a detached task for blocking work). When that is not feasible because the queue itself is the contract, for example a library that must be called from one serial queue, convert to an `async` method that dispatches to that queue inside a checked continuation:
114
161
 
115
162
  ```swift
116
163
  // 1.0
@@ -217,10 +264,6 @@ final class Download: SharedObject {
217
264
 
218
265
  @JS
219
266
  var progress: Double { currentProgress }
220
-
221
- // Installs on the JS class (constructor) object, not the prototype.
222
- @JS
223
- static let maxConcurrent = 4
224
267
  }
225
268
 
226
269
  @ExpoModule(classes: [Download.self])
@@ -229,10 +272,62 @@ final class DownloadModule: Module {}
229
272
 
230
273
  Drop the leading owner argument used by instance DSL closures; use `self` in the real instance method. Preserve constructor arity and JS member names.
231
274
 
232
- Migrate only when the checked-out core supplies the shared-object decoration and construction hooks. Static properties migrate as Swift `static` or `class` properties and install on the JS class (constructor) object rather than the prototype; verify constructor-side routing for static functions before migrating them, since instance support does not imply static-function support. Never create both a 1.0 `Class(...)` entry and a 2.0 registration for the same class without confirming that core intentionally merges them.
275
+ Migrate only when the checked-out core supplies the shared-object decoration and construction hooks. Never create both a 1.0 `Class(...)` entry and a 2.0 registration for the same class without confirming that core intentionally merges them.
233
276
 
234
277
  Static members belong to shared objects, not modules: a module is exported to JS as an instance, so Swift `static` members on a `Module` class are not useful there. Keep module-level values as instance `@JS` members.
235
278
 
279
+ #### Static members
280
+
281
+ A static member installs on the JS class object itself, so JS reaches it as `Download.supportedSchemes()`, not through an instance. The 1.0 DSL has dedicated components for this: `StaticFunction` and `StaticAsyncFunction`. Unlike `Function`, they do not receive the instance as their first argument. There is no static *property* component in 1.0, so a static value was exposed as a `StaticFunction` returning it.
282
+
283
+ ```swift
284
+ // 1.0
285
+ Class(Download.self) {
286
+ Constructor { (url: URL) in Download(url: url) }
287
+
288
+ // Instance function: receives the owner as its first argument.
289
+ Function("pause") { (download: Download) in
290
+ download.pause()
291
+ }
292
+
293
+ // Static: no owner argument, callable as Download.supportedSchemes()
294
+ StaticFunction("supportedSchemes") { () -> [String] in
295
+ Download.supportedSchemes
296
+ }
297
+ }
298
+ ```
299
+
300
+ ```swift
301
+ // 2.0
302
+ @SharedObject
303
+ final class Download: SharedObject {
304
+ @JS
305
+ init(url: URL) { self.url = url }
306
+
307
+ @JS
308
+ func pause() {}
309
+
310
+ @JS
311
+ static func supportedSchemes() -> [String] {
312
+ return Self.schemes
313
+ }
314
+ }
315
+ ```
316
+
317
+ `StaticFunction` and `StaticAsyncFunction` both collapse into a `static` (or `class`) Swift declaration, with `async` carrying the distinction, exactly as `Function`/`AsyncFunction` do for instance members. Because 1.0 had no static property component, a `StaticFunction` that only returns a stored value can become a `@JS static var` in 2.0. That changes the JS contract from a call to a property access, so do it only when the user asks; a syntax migration keeps it a function.
318
+
319
+ What decides whether any of this works is the binding path: instance members bind onto the class prototype, while `static`/`class` members bind onto the constructor object through a separate, `constructor:`-labeled decoration hook. The two are independent capabilities.
320
+
321
+ **Support status.** The macros plugin has emitted the `constructor:` binding since `0.7.0`, but core shipped the `prototype:` overload well ahead of it. Where only `prototype:` is declared, a `@JS static` member expands and then fails to link. Verify before migrating any static member:
322
+
323
+ ```bash
324
+ grep -rn '_decorateSharedObject' <expo-modules-core>
325
+ ```
326
+
327
+ If only the `prototype:` overload is declared, keep every `StaticFunction`/`StaticAsyncFunction` entry in the 1.0 `Class(...)` block. Mixed mode is the expected outcome here: migrate the constructor and instance members, leave the static entries on the DSL, and say in the handoff that static members stayed behind because the installed core does not bind them yet.
328
+
329
+ Static functions and static properties travel this same path, so neither is available ahead of the other. When the hook is present, confirm it by calling the member on the JS class rather than on an instance; an instance-side test passes whether or not static binding works.
330
+
236
331
  ## Records
237
332
 
238
333
  Attach `@Record`, remove field wrappers, and encode requiredness in the declaration:
@@ -256,11 +351,67 @@ Preserve the 1.0 contract exactly. For example, migrate `@Field var source: URL?
256
351
 
257
352
  Every stored property is part of the 2.0 record surface. If the old type contains stored bookkeeping that was not a 1.0 field, move it out of the record or leave the type on 1.0; there is no field opt-out. Verify that each field supports the required `JavaScriptDecodable`/`JavaScriptEncodable` direction.
258
353
 
259
- Do not adopt `@Union` until that macro and its coding witnesses exist in the target.
354
+ ## Unions
355
+
356
+ `@Union` (macros plugin `0.10.0`) is the typed, N-case alternative to 1.0's `Either` types. It applies to a non-generic `enum` whose every case carries exactly one associated value:
357
+
358
+ ```swift
359
+ // 1.0: JS `string | SourceOptions`, unwrapped by probing each side.
360
+ @JS
361
+ func load(_ source: Either<String, SourceOptions>) throws {
362
+ if let text: String = source.get() {
363
+ load(url: URL(string: text))
364
+ } else if let options: SourceOptions = source.get() {
365
+ load(url: options.url, headers: options.headers)
366
+ }
367
+ }
368
+ ```
369
+
370
+ ```swift
371
+ // 2.0: the same JS type, as a named enum.
372
+ @Union
373
+ enum Source {
374
+ case text(String)
375
+ case options(SourceOptions) // a @Record
376
+ }
377
+
378
+ @JS
379
+ func load(_ source: Source) throws {
380
+ switch source { // exhaustive; each payload keeps its static type
381
+ case .text(let text):
382
+ load(url: URL(string: text))
383
+ case .options(let options):
384
+ load(url: options.url, headers: options.headers)
385
+ }
386
+ }
387
+ ```
388
+
389
+ The JS-visible type is unchanged: case order defines the accepted alternatives, so `Either<String, SourceOptions>` maps to cases in that same order. Decoding tries each case's payload in declaration order and the first match wins, which matters for overlapping types (`Int`/`Double`, `URL`/`String`): keep the 1.0 `Either` order, since reordering silently changes which case a given JS value lands in.
390
+
391
+ Besides `switch`, a value unwraps by payload type without naming the case, which is the closest analogue to 1.0's `get()`:
392
+
393
+ ```swift
394
+ let text = try source.as(String.self) // throws on a mismatch
395
+ let options = try? source.as(SourceOptions.self) // SourceOptions?
396
+ ```
397
+
398
+ A type the union does not carry is a compile error, not a runtime `nil`. A mismatch on a held value throws, where 1.0's `get()` returned `nil`, so a migrated probe chain must become a `switch` or a `try?`.
399
+
400
+ Constraints, each a compile error: a generic enum, no cases, a case with no payload or more than one, a default value on the payload, and two cases with the identical payload spelling.
401
+
402
+ **Check availability first.** The plugin side shipped in `0.10.0`, but this also needs the `Union` macro declaration and the `UnionCaseMismatch` exception in core:
403
+
404
+ ```bash
405
+ grep -rn 'public macro Union\|UnionCaseMismatch' <expo-modules-core>
406
+ ```
407
+
408
+ Keep `Either` types on the 1.0 DSL until both exist in the target. Treat adopting `@Union` as an API-shape change to raise with the user rather than a mechanical step: it renames nothing in JS, but it does restructure Swift call sites.
260
409
 
261
410
  ## Views and lifecycle
262
411
 
263
- Keep UIKit `View`, `Prop`, view `Events`, and `OnViewDidUpdateProps` DSL entries until the target includes the complete `@ViewProps`/`@ExpoView` core contract. Macro declarations or expansion tests alone do not prove the runtime update path exists.
412
+ Views are not covered by 2.0 yet. Keep UIKit `View`, `Prop`, view `Events`, and `OnViewDidUpdateProps` DSL entries on 1.0. Macro declarations or expansion tests alone do not prove the runtime update path exists.
413
+
414
+ The planned shape is a class marked `@ExpoView` whose props and event callbacks are declared once in a typed `@ViewProps` struct, instead of split between the native view and a hand-written JS prop type. Until that lands complete, a module with views migrates its non-view members and keeps the view on the DSL. That is a normal mixed-mode result, not a failed migration.
264
415
 
265
416
  Module lifecycle is core-owned rather than macro-generated. The DSL components map to hook methods with no-op defaults:
266
417
 
@@ -289,7 +440,7 @@ Before deleting `definition()`, verify that it contains no:
289
440
  - views or view events
290
441
  - lifecycle or app-context listeners
291
442
  - queue-pinned functions
292
- - shared-object static functions or other unsupported definitions
443
+ - `StaticFunction`/`StaticAsyncFunction` entries, unless core declares the `constructor:` decoration hook
293
444
 
294
445
  ## Contract checklist
295
446
 
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: expo-native-ui
3
- description: Framework (OSS). Build beautiful, native-feeling Expo screens. Covers Apple HIG styling, semantic colors, native controls, SF Symbols, media, animations, visual effects, gradients, storage, and responsive layout. For routing and navigation, use the expo-router skill.
3
+ description: Framework (OSS). Build beautiful, native-feeling Expo screens. Covers Apple HIG styling, semantic colors, native controls, SF Symbols, media, visual effects, gradients, storage, and responsive layout. For routing and navigation, use the expo-router skill; for motion and animation, use the expo-animation skill.
4
4
  version: 1.1.1
5
5
  license: MIT
6
6
  ---
7
7
 
8
8
  # Expo Native UI Guidelines
9
9
 
10
- For routes, links, stacks, tabs, modals, sheets, and headers, use the `expo-router` skill.
10
+ For routes, links, stacks, tabs, modals, sheets, and headers, use the `expo-router` skill. For any motion — entering/exiting, gestures, springs, keyboard-driven UI — use the `expo-animation` skill.
11
11
 
12
- > **Before picking any UI component, check `expo-ui` first.** `@expo/ui` provides native equivalents — BottomSheet, Button, Picker, Slider, Menu, Section, Switch, SegmentedControl, and more — rendered as real SwiftUI on iOS and Jetpack Compose on Android, available in Expo Go on SDK 56+ with no custom build. Load the **`expo-ui`** skill to find the right component before falling back to React Native built-ins or community libraries. This skill (`expo-native-ui`) covers the surrounding structure: Expo Router navigation, layout, styling, and animations.
12
+ > **Before picking any UI component, check `expo-ui` first.** `@expo/ui` provides native equivalents — BottomSheet, Button, Picker, Slider, Menu, Section, Switch, SegmentedControl, and more — rendered as real SwiftUI on iOS and Jetpack Compose on Android, available in Expo Go on SDK 56+ with no custom build. Load the **`expo-ui`** skill to find the right component before falling back to React Native built-ins or community libraries. This skill (`expo-native-ui`) covers the surrounding structure: Expo Router navigation, layout, styling, and visual effects.
13
13
 
14
14
  ## References
15
15
 
@@ -17,10 +17,9 @@ Consult these resources as needed:
17
17
 
18
18
  ```
19
19
  references/
20
- animations.md Reanimated: entering, exiting, layout, scroll-driven, gestures
21
20
  controls.md Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
22
21
  gradients.md CSS gradients via experimental_backgroundImage (New Arch only)
23
- icons.md SF Symbols via expo-image (sf: source), names, animations, weights
22
+ icons.md SF Symbols via expo-symbols SymbolView: names, weights, animations; Material icons on Android
24
23
  media.md Camera, audio, video, and file saving
25
24
  storage.md SQLite, AsyncStorage, SecureStore
26
25
  visual-effects.md Blur (expo-blur) and liquid glass (expo-glass-effect)
@@ -48,12 +47,11 @@ You need `npx expo run:ios/android` or `eas build` ONLY when using:
48
47
 
49
48
  ### When Expo Go Works
50
49
 
51
- Expo Go supports a huge range of features out of the box:
50
+ Expo Go supports a wide range of features out of the box:
52
51
 
53
- - All `expo-*` packages (camera, location, notifications, etc.)
54
- - Expo Router navigation
52
+ - Most `expo-*` packages (camera, location, sensors, sqlite, etc.) — but not all: remote push notifications don't work in Expo Go on Android since SDK 53, and some packages need native capabilities Expo Go doesn't bundle (e.g. WebGPU — see `references/webgpu-three.md`)
53
+ - Expo Router navigation and deep links
55
54
  - Most UI libraries (reanimated, gesture handler, etc.)
56
- - Push notifications, deep links, and more
57
55
 
58
56
  **If you're unsure, try Expo Go first.** Creating custom builds adds complexity, slower iteration, and requires Xcode/Android Studio setup.
59
57
 
@@ -72,7 +70,7 @@ Expo Go supports a huge range of features out of the box:
72
70
  - Never use legacy expo-permissions
73
71
  - `expo-audio` not `expo-av`
74
72
  - `expo-video` not `expo-av`
75
- - `expo-image` with `source="sf:name"` for SF Symbols, not `expo-symbols` or `@expo/vector-icons`
73
+ - `expo-symbols` (`SymbolView`) for SF Symbols on iOS, not `@expo/vector-icons` — see `references/icons.md`. SF Symbols are Apple-only: on Android every icon needs a Material source (`md` prop on NativeTabs triggers; in-screen options under "Android: Material Icons" in icons.md), never SF-only iconography
76
74
  - `react-native-safe-area-context` not react-native SafeAreaView
77
75
  - `process.env.EXPO_OS` not `Platform.OS`
78
76
  - `React.use` not `React.useContext`
@@ -83,7 +81,7 @@ Expo Go supports a huge range of features out of the box:
83
81
 
84
82
  ## Responsiveness
85
83
 
86
- - Always wrap root component in a scroll view for responsiveness
84
+ - Wrap screens with scrollable content in a ScrollView. Screens whose root is a FlatList/FlashList must not add an outer ScrollView (the list is the scroll container), and full-bleed screens (camera, map, canvas) need neither
87
85
  - Use `<ScrollView contentInsetAdjustmentBehavior="automatic" />` instead of `<SafeAreaView>` for smarter safe area insets
88
86
  - `contentInsetAdjustmentBehavior="automatic"` should be applied to FlatList and SectionList as well
89
87
  - Use flexbox instead of Dimensions API
@@ -93,15 +91,21 @@ Expo Go supports a huge range of features out of the box:
93
91
 
94
92
  - Use expo-haptics conditionally on iOS to make more delightful experiences
95
93
  - Use views with built-in haptics like `<Switch />` from React Native and `@react-native-community/datetimepicker`
96
- - When a route belongs to a Stack, its first child should almost always be a ScrollView with `contentInsetAdjustmentBehavior="automatic"` set
97
- - When adding a `ScrollView` to the page it should almost always be the first component inside the route component
94
+ - When a Stack route has scrollable content, make the ScrollView (or FlatList) the first component inside the route, with `contentInsetAdjustmentBehavior="automatic"` set
98
95
  - Use the `<Text selectable />` prop on text containing data that could be copied
99
96
  - Consider formatting large numbers like 1.4M or 38k
100
97
  - Never use intrinsic elements like 'img' or 'div' unless in a webview or Expo DOM component
98
+ - Every screen that loads data has four states (loading, error, empty, content) - never show the empty state while the first load is still resolving; the rules live in the `expo-data-fetching` skill
99
+ - On scrollable forms and search results, use `keyboardShouldPersistTaps="handled"` so controls receive the first tap and unhandled taps can dismiss the keyboard. Use `"always"` only when unhandled taps should also keep it open
100
+ - A form's primary action must never sit under the keyboard. For UI that tracks the keyboard's real frame, load the `expo-animation` skill's keyboard recipe (`react-native-keyboard-controller`) - never `Keyboard.addListener` plus a timing animation
101
+ - Every enabled control must perform its advertised action: search filters results, Save commits edits, and settings affect behavior. Empty handlers and success alerts are not implementations; local state is enough when the user requested a prototype
102
+ - For async saves, preserve drafts and handle pending/failure states per `expo-data-fetching`; do not dismiss a form before its save succeeds
103
+
104
+ Before calling a screen complete, walk through its primary task, including one failure and recovery when it loads or saves data. Check keyboard access and back/dismiss behavior. Try long titles, missing images, no search results, and large system text; required actions must remain reachable. Report what you exercised and what you could not run.
101
105
 
102
106
  # Styling
103
107
 
104
- Follow Apple Human Interface Guidelines.
108
+ Follow each platform's own design language: Apple Human Interface Guidelines on iOS, Material Design 3 on Android. Never dress one platform in the other's uniform - no FAB or ripple in iOS layouts; no hand-built iOS chrome (back-chevrons, large-title text, iOS-styled switches) on Android.
105
109
 
106
110
  ## General Styling Rules
107
111
 
@@ -110,7 +114,7 @@ Follow Apple Human Interface Guidelines.
110
114
  - Always account for safe area, either with stack headers, tabs, or ScrollView/FlatList `contentInsetAdjustmentBehavior="automatic"`
111
115
  - Ensure both top and bottom safe area insets are accounted for
112
116
  - Inline styles not StyleSheet.create unless reusing styles is faster
113
- - Add entering and exiting animations for state changes
117
+ - For any motion or animation work, load the `expo-animation` skill — it owns the animate-or-not decision, timing values, and interruption rules
114
118
  - Use `{ borderCurve: 'continuous' }` for rounded corners unless creating a capsule shape
115
119
  - ALWAYS use a navigation stack title instead of a custom text element on the page
116
120
  - When padding a ScrollView, use `contentContainerStyle` padding and gap instead of padding on the ScrollView itself (reduces clipping)
@@ -148,6 +152,11 @@ export const colors = {
148
152
  android: Color.android.dynamic.surface,
149
153
  default: "#ffffff",
150
154
  })!,
155
+ secondarySystemBackground: Platform.select({
156
+ ios: Color.ios.secondarySystemBackground,
157
+ android: Color.android.dynamic.surfaceVariant,
158
+ default: "#f2f2f7",
159
+ })!,
151
160
  systemBlue: Platform.select({
152
161
  ios: Color.ios.systemBlue,
153
162
  android: Color.android.dynamic.primary,
@@ -165,7 +174,7 @@ import { colors } from "@/theme/colors";
165
174
  ```
166
175
 
167
176
  - iOS re-resolves these colors automatically when the system theme changes. On Android, call `useColorScheme()` inside any component that renders them so it re-renders when the theme flips (required when React Compiler memoizes the component).
168
- - Don't pass `Color` / `PlatformColor` values into Reanimated styles — use static colors there (see `references/animations.md`).
177
+ - Don't pass `Color` / `PlatformColor` values into Reanimated styles — they are opaque native color objects, not strings; use static colors there.
169
178
  - `Platform.select({...})!` returns `string | OpaqueColorValue`. Most React Native style props accept `ColorValue` (`string | OpaqueColorValue`) so this works fine. But some third-party props only accept `string` (e.g. `tintColor` on `expo-image`). Cast when needed: `colors.label as string`.
170
179
 
171
180
  ## Text Styling
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Expo Native UI"
3
- short_description: "Build beautiful, native-feeling Expo screens: HIG styling, semantic colors, controls, SF Symbols, media, animation, and visual effects"
4
- default_prompt: "Use $expo-native-ui to style native-feeling Expo screens, choose semantic colors and native controls, add media, animations, and visual effects, and decide when Expo Go is enough before creating native builds."
3
+ short_description: "Build beautiful, native-feeling Expo screens: HIG styling, semantic colors, controls, SF Symbols, media, and visual effects"
4
+ default_prompt: "Use $expo-native-ui to style native-feeling Expo screens, choose semantic colors and native controls, add media and visual effects, and decide when Expo Go is enough before creating native builds."
@@ -17,17 +17,7 @@ const [enabled, setEnabled] = useState(false);
17
17
  <Switch value={enabled} onValueChange={setEnabled} />;
18
18
  ```
19
19
 
20
- ### Customization
21
-
22
- ```tsx
23
- <Switch
24
- value={enabled}
25
- onValueChange={setEnabled}
26
- trackColor={{ false: "#767577", true: "#81b0ff" }}
27
- thumbColor={enabled ? "#f5dd4b" : "#f4f3f4"}
28
- ios_backgroundColor="#3e3e3e"
29
- />
30
- ```
20
+ Don't recolor the Switch — native styling adapts to dark mode automatically.
31
21
 
32
22
  ## Segmented Control
33
23
 
@@ -83,21 +73,6 @@ const [value, setValue] = useState(0.5);
83
73
  />;
84
74
  ```
85
75
 
86
- ### Customization
87
-
88
- ```tsx
89
- <Slider
90
- value={value}
91
- onValueChange={setValue}
92
- minimumValue={0}
93
- maximumValue={100}
94
- step={1}
95
- minimumTrackTintColor="#007AFF"
96
- maximumTrackTintColor="#E5E5EA"
97
- thumbTintColor="#007AFF"
98
- />
99
- ```
100
-
101
76
  ### Discrete Steps
102
77
 
103
78
  ```tsx
@@ -174,39 +149,22 @@ const [date, setDate] = useState(new Date());
174
149
  />
175
150
  ```
176
151
 
177
- ## Stepper
178
-
179
- Increment/decrement numeric values.
180
-
181
- ```tsx
182
- import { Stepper } from "react-native";
183
- import { useState } from "react";
184
-
185
- const [count, setCount] = useState(0);
186
-
187
- <Stepper
188
- value={count}
189
- onValueChange={setCount}
190
- minimumValue={0}
191
- maximumValue={10}
192
- />;
193
- ```
194
-
195
152
  ## TextInput
196
153
 
197
154
  Native text input with various keyboard types.
198
155
 
199
156
  ```tsx
200
157
  import { TextInput } from "react-native";
158
+ import { colors } from "@/theme/colors";
201
159
 
202
160
  <TextInput
203
161
  placeholder="Enter text..."
204
- placeholderTextColor="#999"
162
+ placeholderTextColor={colors.secondaryLabel as string}
205
163
  style={{
206
164
  padding: 12,
207
165
  fontSize: 16,
208
166
  borderRadius: 8,
209
- backgroundColor: "#f0f0f0",
167
+ backgroundColor: colors.secondarySystemBackground,
210
168
  }}
211
169
  />
212
170
  ```
@@ -265,6 +223,7 @@ const [selected, setSelected] = useState("js");
265
223
  ## Best Practices
266
224
 
267
225
  - **Haptics**: Switch and DateTimePicker have built-in haptics — don't add extra
226
+ - **No native Stepper**: React Native has no `Stepper` component — compose increment/decrement from two Buttons or a numeric TextInput
268
227
  - **Accessibility**: Native controls have proper accessibility labels by default
269
228
  - **Dark Mode**: Avoid custom colors — native styling adapts automatically
270
229
  - **Spacing**: Use consistent padding around controls (12-16pt)
@@ -1,6 +1,6 @@
1
1
  # Icons (SF Symbols)
2
2
 
3
- Use SF Symbols for native feel. Never use FontAwesome or Ionicons.
3
+ Use SF Symbols on iOS for native feel. SF Symbols are an Apple asset and never render on Android - every icon needs a Material counterpart (see "Android: Material Icons" below). Never use FontAwesome or Ionicons on either platform, and never emoji as icons.
4
4
 
5
5
  ## Basic Usage
6
6
 
@@ -198,6 +198,25 @@ Some symbols support multiple colors:
198
198
  />
199
199
  ```
200
200
 
201
+ ## Android: Material Icons
202
+
203
+ On Android an SF Symbol source renders nothing - an app that ships SF-only iconography ships blank icons. Give every icon a Material source:
204
+
205
+ - **Tab bars**: pass `md` (Material Symbol name) beside `sf` on NativeTabs triggers - `<NativeTabs.Trigger.Icon sf="gear" md="settings" />` (SDK 55+, see the `expo-router` skill).
206
+ - **In screens**: `@expo/vector-icons` Material families, gated by platform:
207
+
208
+ ```tsx
209
+ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
210
+
211
+ process.env.EXPO_OS === "ios" ? (
212
+ <SymbolView name="gear" tintColor={colors.label} style={{ width: 24, height: 24 }} />
213
+ ) : (
214
+ <MaterialIcons name="settings" size={24} color={colors.label} />
215
+ );
216
+ ```
217
+
218
+ One icon family per platform - never a mixed set, never SF names on Android or Material glyphs on iOS.
219
+
201
220
  ## Finding Symbol Names
202
221
 
203
222
  1. Use the SF Symbols app on macOS (free from Apple)
@@ -206,7 +225,7 @@ Some symbols support multiple colors:
206
225
 
207
226
  ## Best Practices
208
227
 
209
- - Always use SF Symbols over vector icon libraries
228
+ - On iOS, always use SF Symbols over vector icon libraries; on Android, use Material icons (see above)
210
229
  - Match symbol weight to nearby text weight
211
230
  - Use `.fill` variants for selected/active states
212
231
  - Use the cross-platform `colors` helper (see SKILL.md "Colors") for tint to support dark mode
@@ -6,17 +6,17 @@
6
6
  - Ensure to flip the camera with `mirror` to emulate social apps
7
7
  - Use liquid glass buttons on cameras
8
8
  - Icons: `arrow.triangle.2.circlepath` (flip), `photo` (gallery), `bolt` (flash)
9
- - Eagerly request camera permission
10
- - Lazily request media library permission
9
+ - Gate the camera behind an explanation screen with a Grant button (as below) — never fire the permission prompt on mount
10
+ - Lazily request media library permission (at first save/pick)
11
+
12
+ The example uses the `GlassButton` helper defined in `visual-effects.md` (Glass Buttons section).
11
13
 
12
14
  ```tsx
13
15
  import React, { useRef, useState } from "react";
14
- import { View, TouchableOpacity, Text, Alert } from "react-native";
16
+ import { View, Pressable, Text } from "react-native";
15
17
  import { CameraView, CameraType, useCameraPermissions } from "expo-camera";
16
- import * as MediaLibrary from "expo-media-library";
17
18
  import * as ImagePicker from "expo-image-picker";
18
19
  import * as Haptics from "expo-haptics";
19
- import { SymbolView } from "expo-symbols";
20
20
  import { colors } from "@/theme/colors";
21
21
  import { GlassView } from "expo-glass-effect";
22
22
  import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -32,9 +32,9 @@ function Camera({ onPicture }: { onPicture: (uri: string) => Promise<void> }) {
32
32
  <View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: colors.systemBackground }}>
33
33
  <Text style={{ color: colors.label, padding: 16 }}>Camera access is required</Text>
34
34
  <GlassView isInteractive tintColor={colors.systemBlue} style={{ borderRadius: 12 }}>
35
- <TouchableOpacity onPress={requestPermission} style={{ padding: 12, borderRadius: 12 }}>
35
+ <Pressable onPress={requestPermission} style={{ padding: 12, borderRadius: 12 }}>
36
36
  <Text style={{ color: "white" }}>Grant Permission</Text>
37
- </TouchableOpacity>
37
+ </Pressable>
38
38
  </GlassView>
39
39
  </View>
40
40
  );
@@ -64,7 +64,7 @@ function Camera({ onPicture }: { onPicture: (uri: string) => Promise<void> }) {
64
64
  <CameraView ref={cameraRef} mirror style={{ flex: 1 }} facing={type} />
65
65
  <View style={{ position: "absolute", left: 0, right: 0, bottom: bottom, gap: 16, alignItems: "center" }}>
66
66
  <GlassView isInteractive style={{ padding: 8, borderRadius: 99 }}>
67
- <TouchableOpacity onPress={takePhoto} style={{ width: 64, height: 64, borderRadius: 99, backgroundColor: "white" }} />
67
+ <Pressable onPress={takePhoto} style={{ width: 64, height: 64, borderRadius: 99, backgroundColor: "white" }} />
68
68
  </GlassView>
69
69
  <View style={{ flexDirection: "row", justifyContent: "space-around", paddingHorizontal: 8 }}>
70
70
  <GlassButton onPress={selectPhoto} icon="photo" />
@@ -98,31 +98,26 @@ import {
98
98
  setAudioModeAsync,
99
99
  useAudioRecorderState,
100
100
  } from 'expo-audio';
101
- import { useEffect } from 'react';
102
101
  import { Alert, Button } from 'react-native';
103
102
 
104
103
  function App() {
105
104
  const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
106
105
  const recorderState = useAudioRecorderState(audioRecorder);
107
106
 
107
+ // Request the microphone on first record, not on mount — no prompt before user intent
108
108
  const record = async () => {
109
+ const status = await AudioModule.requestRecordingPermissionsAsync();
110
+ if (!status.granted) {
111
+ Alert.alert('Permission to access microphone was denied');
112
+ return;
113
+ }
114
+ await setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true });
109
115
  await audioRecorder.prepareToRecordAsync();
110
116
  audioRecorder.record();
111
117
  };
112
118
 
113
119
  const stop = () => audioRecorder.stop();
114
120
 
115
- useEffect(() => {
116
- (async () => {
117
- const status = await AudioModule.requestRecordingPermissionsAsync();
118
- if (status.granted) {
119
- setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true });
120
- } else {
121
- Alert.alert('Permission to access microphone was denied');
122
- }
123
- })();
124
- }, []);
125
-
126
121
  return (
127
122
  <Button
128
123
  title={recorderState.isRecording ? 'Stop' : 'Start'}
@@ -142,24 +142,22 @@ function GlassButton({ icon, onPress }) {
142
142
 
143
143
  ### Checking Availability
144
144
 
145
+ Check both guards: `isLiquidGlassAvailable()` (OS support) and `isGlassEffectAPIAvailable()` — some iOS 26 beta versions lack the API and crash without the second check.
146
+
145
147
  ```tsx
146
- import { isLiquidGlassAvailable } from "expo-glass-effect";
148
+ import { isLiquidGlassAvailable, isGlassEffectAPIAvailable } from "expo-glass-effect";
147
149
 
148
- if (isLiquidGlassAvailable()) {
149
- // Use GlassView
150
- } else {
151
- // Fallback to BlurView or solid background
152
- }
150
+ const canUseGlass = isLiquidGlassAvailable() && isGlassEffectAPIAvailable();
153
151
  ```
154
152
 
155
153
  ### Fallback Pattern
156
154
 
157
155
  ```tsx
158
- import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
156
+ import { GlassView, isLiquidGlassAvailable, isGlassEffectAPIAvailable } from "expo-glass-effect";
159
157
  import { BlurView } from "expo-blur";
160
158
 
161
159
  function AdaptiveGlass({ children, style }) {
162
- if (isLiquidGlassAvailable()) {
160
+ if (isLiquidGlassAvailable() && isGlassEffectAPIAvailable()) {
163
161
  return <GlassView style={style}>{children}</GlassView>;
164
162
  }
165
163
 
@@ -171,6 +169,8 @@ function AdaptiveGlass({ children, style }) {
171
169
  }
172
170
  ```
173
171
 
172
+ When the user has Reduce Transparency enabled (`AccessibilityInfo.isReduceTransparencyEnabled()`), skip glass *and* blur — render a solid `colors.systemBackground` surface instead.
173
+
174
174
  ## Sheet with Glass Background
175
175
 
176
176
  Make sheet backgrounds liquid glass on iOS 26+:
@@ -190,8 +190,9 @@ Make sheet backgrounds liquid glass on iOS 26+:
190
190
  ## Best Practices
191
191
 
192
192
  - Use `systemMaterial` tints for automatic dark mode support
193
- - Always set `overflow: 'hidden'` on BlurView for rounded corners
194
- - Use `isInteractive` on GlassView for buttons and pressables
195
- - Check `isLiquidGlassAvailable()` and provide fallbacks
193
+ - Always set `overflow: 'hidden'` on BlurView for rounded corners — but never on a `GlassView` or its ancestors: glass clips itself via `borderRadius` (+ `borderCurve`), and outside clipping cuts off the rim highlight and press bulge
194
+ - Use `isInteractive` on GlassView only for actual controls (buttons, pressables) — leave background surfaces non-interactive
195
+ - Check `isLiquidGlassAvailable()` **and** `isGlassEffectAPIAvailable()`, provide fallbacks, and respect Reduce Transparency with a solid surface
196
+ - Never animate opacity on a `GlassView` or an ancestor — animate content around a stable glass surface, or switch `glassEffectStyle` on the mounted view to change its look
196
197
  - Avoid nesting blur views (performance impact)
197
198
  - Keep blur intensity reasonable (50-100) for readability