@lowdefy/shell 0.0.0-experimental-20261019052246

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 (239) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +308 -0
  3. package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
  4. package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
  5. package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
  6. package/app/+native-intent.js +30 -0
  7. package/app/_layout.js +33 -0
  8. package/app.config.ts +168 -0
  9. package/applyShellPlugins.js +43 -0
  10. package/authCallbackIntentFilters.js +46 -0
  11. package/babel.config.js +25 -0
  12. package/checkShellConfig.js +145 -0
  13. package/checkShellPlugins.js +74 -0
  14. package/getPrivacyManifest.js +39 -0
  15. package/metro.config.js +73 -0
  16. package/native-fingerprint.json +12 -0
  17. package/package.json +130 -0
  18. package/profiles.js +274 -0
  19. package/readShellConfig.js +36 -0
  20. package/resolveShellPlugins.js +109 -0
  21. package/runtimeVersion.js +105 -0
  22. package/scripts/checkReleaseBundle.mjs +358 -0
  23. package/scripts/createDayjsLocales.mjs +67 -0
  24. package/scripts/createShellAssets.mjs +54 -0
  25. package/scripts/createShellVersion.mjs +83 -0
  26. package/scripts/createTypeManifest.mjs +205 -0
  27. package/shell.dev.json +11 -0
  28. package/shellVariant.js +111 -0
  29. package/src/ShellContext.js +32 -0
  30. package/src/actions/CopyToClipboard.js +29 -0
  31. package/src/actions/CopyToClipboard.test.js +33 -0
  32. package/src/actions/GeolocationCurrentPosition.js +55 -0
  33. package/src/actions/GeolocationCurrentPosition.test.js +66 -0
  34. package/src/actions/GetLocalStorage.js +36 -0
  35. package/src/actions/RemoveLocalStorage.js +31 -0
  36. package/src/actions/ScrollTo.js +66 -0
  37. package/src/actions/ScrollTo.test.js +83 -0
  38. package/src/actions/SetFocus.js +33 -0
  39. package/src/actions/SetFocus.test.js +49 -0
  40. package/src/actions/SetLocalStorage.js +34 -0
  41. package/src/actions/localStorage.test.js +68 -0
  42. package/src/actions/measureInScrollView.js +36 -0
  43. package/src/actions/nativeActions.js +41 -0
  44. package/src/auth/AuthButton.js +44 -0
  45. package/src/auth/AuthGate.js +57 -0
  46. package/src/auth/AuthLink.js +43 -0
  47. package/src/auth/LockScreen.js +67 -0
  48. package/src/auth/LockScreen.test.js +37 -0
  49. package/src/auth/SignInScreen.js +256 -0
  50. package/src/auth/SignInScreen.test.js +182 -0
  51. package/src/auth/createAppAuth.js +306 -0
  52. package/src/auth/createAppAuth.test.js +414 -0
  53. package/src/auth/createAuthApi.js +99 -0
  54. package/src/auth/createAuthApi.test.js +135 -0
  55. package/src/auth/createCoreAuth.js +96 -0
  56. package/src/auth/createCoreAuth.test.js +188 -0
  57. package/src/auth/createSessionStores.js +45 -0
  58. package/src/auth/createTokenStore.js +103 -0
  59. package/src/auth/createTokenStore.test.js +116 -0
  60. package/src/auth/getAuthSettings.js +24 -0
  61. package/src/auth/getAuthView.js +39 -0
  62. package/src/auth/getAuthView.test.js +52 -0
  63. package/src/auth/getSheetReturnUrl.js +35 -0
  64. package/src/auth/getTokenStoreOptions.js +46 -0
  65. package/src/auth/isBackgroundShell.js +26 -0
  66. package/src/auth/isSheetReturnLink.js +35 -0
  67. package/src/auth/parseQuery.js +39 -0
  68. package/src/auth/purgeAppData.js +45 -0
  69. package/src/auth/shouldLock.js +26 -0
  70. package/src/auth/signInWithSheet.js +97 -0
  71. package/src/auth/signInWithSheet.test.js +205 -0
  72. package/src/auth/styles.js +78 -0
  73. package/src/auth/unregisterPushToken.js +21 -0
  74. package/src/auth/usesBiometricUnlock.js +23 -0
  75. package/src/auth/watchSession.js +47 -0
  76. package/src/auth/withoutCookies.js +26 -0
  77. package/src/cache/createSqliteCacheStore.js +113 -0
  78. package/src/cache/createSqliteCacheStore.test.js +93 -0
  79. package/src/cache/getCacheKey.js +25 -0
  80. package/src/cache/getLastCacheKeyName.js +22 -0
  81. package/src/components/AppProvider.js +90 -0
  82. package/src/components/Frame.js +116 -0
  83. package/src/components/Frame.test.js +101 -0
  84. package/src/components/LoadingScreen.js +35 -0
  85. package/src/components/MenuButton.js +108 -0
  86. package/src/components/MenuButton.test.js +51 -0
  87. package/src/components/MessageScreen.js +51 -0
  88. package/src/components/NavigationTheme.js +33 -0
  89. package/src/components/NoConnectionBanner.js +51 -0
  90. package/src/components/NoConnectionBanner.test.js +73 -0
  91. package/src/components/OtherPageScreen.js +56 -0
  92. package/src/components/PageScreen.js +244 -0
  93. package/src/components/PageScreen.test.js +492 -0
  94. package/src/components/ShellRoot.js +34 -0
  95. package/src/components/ShellRoot.test.js +62 -0
  96. package/src/components/ShellThemeProvider.js +45 -0
  97. package/src/components/SwitchToTab.js +36 -0
  98. package/src/components/SwitchToTab.test.js +63 -0
  99. package/src/components/TabIconImages.js +72 -0
  100. package/src/components/UpdateScreen.js +36 -0
  101. package/src/components/getNavigationTheme.js +44 -0
  102. package/src/components/getNavigationTheme.test.js +41 -0
  103. package/src/components/styles.js +50 -0
  104. package/src/components/useSurfaceTokens.js +47 -0
  105. package/src/config/getAppApiBase.js +31 -0
  106. package/src/config/getShellConfig.js +35 -0
  107. package/src/config/getShellConfig.test.js +80 -0
  108. package/src/createShellApp.js +105 -0
  109. package/src/dev/DevShell.js +228 -0
  110. package/src/dev/DevShell.test.js +233 -0
  111. package/src/dev/connect/ConfirmSheet.js +71 -0
  112. package/src/dev/connect/ConnectScreen.js +117 -0
  113. package/src/dev/connect/QrScanner.js +89 -0
  114. package/src/dev/connection/createDevShellConfig.js +47 -0
  115. package/src/dev/connection/createDevTransport.js +51 -0
  116. package/src/dev/connection/createDevTransport.test.js +87 -0
  117. package/src/dev/connection/createServerList.js +48 -0
  118. package/src/dev/connection/isLoopbackHost.js +25 -0
  119. package/src/dev/connection/pairedShellApp.test.js +104 -0
  120. package/src/dev/connection/parseDevLink.js +66 -0
  121. package/src/dev/connection/parseDevLink.test.js +97 -0
  122. package/src/dev/connection/parseQuery.js +33 -0
  123. package/src/dev/connection/parseServerInput.js +40 -0
  124. package/src/dev/connection/parseServerUrl.js +45 -0
  125. package/src/dev/devStyles.js +97 -0
  126. package/src/dev/index.js +21 -0
  127. package/src/dev/inject/createDeviceInjections.js +185 -0
  128. package/src/dev/inject/createDeviceInjections.test.js +208 -0
  129. package/src/dev/inject/createOfflineFetch.js +35 -0
  130. package/src/dev/inject/createOfflineFetch.test.js +68 -0
  131. package/src/dev/menu/BlockGallery.js +33 -0
  132. package/src/dev/menu/DevMenu.js +121 -0
  133. package/src/dev/menu/createLogBuffer.js +83 -0
  134. package/src/dev/menu/watchShake.js +52 -0
  135. package/src/dev/menu/watchShake.test.js +61 -0
  136. package/src/dev/recorder/createNativeRecorder.js +204 -0
  137. package/src/dev/recorder/createNativeRecorder.test.js +307 -0
  138. package/src/dev/redirectDevLink.js +30 -0
  139. package/src/dev/redirectDevLink.test.js +49 -0
  140. package/src/dev/steps/createStepRunner.js +184 -0
  141. package/src/dev/steps/createStepRunner.test.js +262 -0
  142. package/src/dev/steps/findBlock.js +78 -0
  143. package/src/dev/steps/isContextSettled.js +37 -0
  144. package/src/dev/steps/waitForSettle.js +39 -0
  145. package/src/dev/steps/waitUntil.js +40 -0
  146. package/src/dev/tab/DevTabClient.js +97 -0
  147. package/src/dev/tab/answerEval.js +39 -0
  148. package/src/dev/tab/answerInspect.js +45 -0
  149. package/src/dev/tab/answerLoadState.js +50 -0
  150. package/src/dev/tab/createDevTabClient.js +277 -0
  151. package/src/dev/tab/createDevTabClient.test.js +314 -0
  152. package/src/dev/tab/devClientMarker.js +22 -0
  153. package/src/dev/tab/devClientMarker.test.js +23 -0
  154. package/src/dev/tab/findPageContext.js +43 -0
  155. package/src/dev/tab/getShownPage.js +42 -0
  156. package/src/dev/tab/readEventStream.js +68 -0
  157. package/src/dev/tab/reloadApp.js +42 -0
  158. package/src/dev/tab/reloadApp.test.js +115 -0
  159. package/src/device/injected.js +60 -0
  160. package/src/device/injected.test.js +39 -0
  161. package/src/host/createDeviceStore.js +32 -0
  162. package/src/host/createMemoryStore.js +32 -0
  163. package/src/host/createNetworkStatus.js +94 -0
  164. package/src/host/createNetworkStatus.test.js +94 -0
  165. package/src/host/createRouterAdapter.js +33 -0
  166. package/src/host/createShellFetch.js +60 -0
  167. package/src/host/createShellFetch.test.js +150 -0
  168. package/src/host/createShellGlobals.js +62 -0
  169. package/src/host/createShellHost.js +73 -0
  170. package/src/host/createShellHost.test.js +36 -0
  171. package/src/host/getOrigin.js +26 -0
  172. package/src/host/isOnline.js +22 -0
  173. package/src/host/networkStatus.js +24 -0
  174. package/src/host/withShellHeader.js +25 -0
  175. package/src/host/withShellHeader.test.js +28 -0
  176. package/src/js/evaluateJsEntries.js +43 -0
  177. package/src/js/evaluateJsEntries.test.js +48 -0
  178. package/src/locale/createShellLocale.js +112 -0
  179. package/src/locale/createShellLocale.test.js +74 -0
  180. package/src/locale/dayjsLocales.js +165 -0
  181. package/src/locale/getDeviceLocales.js +23 -0
  182. package/src/locale/loadDayjsLocale.js +32 -0
  183. package/src/locale/resolveActiveLocale.js +46 -0
  184. package/src/locale/resolveActiveLocale.test.js +40 -0
  185. package/src/navigation/SlotContext.js +24 -0
  186. package/src/navigation/frameSlots.js +27 -0
  187. package/src/navigation/getFrame.js +30 -0
  188. package/src/navigation/getInitialScreenOptions.js +63 -0
  189. package/src/navigation/getInitialScreenOptions.test.js +66 -0
  190. package/src/navigation/getPresentation.js +28 -0
  191. package/src/navigation/getSlotRootPath.js +27 -0
  192. package/src/navigation/getStackOptions.js +185 -0
  193. package/src/navigation/getTabHref.js +45 -0
  194. package/src/navigation/getTabIcons.js +43 -0
  195. package/src/navigation/isPageUnderHeader.js +46 -0
  196. package/src/navigation/navigation.test.js +342 -0
  197. package/src/navigation/toRouterHref.js +29 -0
  198. package/src/plugins/appTypes.js +28 -0
  199. package/src/plugins/prefixTypes.js +28 -0
  200. package/src/plugins/shellPlugins.json +14 -0
  201. package/src/plugins/typeManifest.js +649 -0
  202. package/src/plugins/types.js +61 -0
  203. package/src/plugins/types.test.js +173 -0
  204. package/src/polyfills/installGetRandomValues.js +28 -0
  205. package/src/session/createAppSession.js +318 -0
  206. package/src/session/createAppSession.test.js +359 -0
  207. package/src/session/getPageView.js +59 -0
  208. package/src/session/getPageView.test.js +137 -0
  209. package/src/sync/encodePagePath.js +27 -0
  210. package/src/sync/fetchPage.js +49 -0
  211. package/src/sync/fetchRoot.js +38 -0
  212. package/src/sync/fetchUser.js +28 -0
  213. package/src/sync/runLimited.js +36 -0
  214. package/src/sync/toRootConfig.js +35 -0
  215. package/src/test/createFakeNetwork.js +40 -0
  216. package/src/test/createFakeSecureStore.js +40 -0
  217. package/src/test/createFakeServer.js +98 -0
  218. package/src/test/createMemoryCacheStore.js +59 -0
  219. package/src/test/expoImage.js +38 -0
  220. package/src/test/expoSpeechRecognition.js +31 -0
  221. package/src/test/expoUi.js +55 -0
  222. package/src/test/expoUiBottomSheet.js +64 -0
  223. package/src/test/expoUiDateTimePicker.js +26 -0
  224. package/src/test/expoUiMenu.js +46 -0
  225. package/src/test/expoUiSegmentedControl.js +47 -0
  226. package/src/test/expoUiSwiftUi.js +62 -0
  227. package/src/test/expoUiSwiftUiModifiers.js +23 -0
  228. package/src/test/expoWebBrowser.js +23 -0
  229. package/src/test/fixtures.js +98 -0
  230. package/src/test/renderPageScreen.js +109 -0
  231. package/src/types/checkTypeSet.js +39 -0
  232. package/src/types/collectPageTypes.js +49 -0
  233. package/src/types/collectPageTypes.test.js +90 -0
  234. package/src/types/findMissingPageTypes.js +40 -0
  235. package/src/version/compareVersions.js +38 -0
  236. package/src/version/isShellTooOld.js +30 -0
  237. package/src/version/shellVersion.js +21 -0
  238. package/src/version/shellVersion.test.js +32 -0
  239. package/withExcludedNativeModules.js +93 -0
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # @lowdefy/shell
2
+
3
+ The Expo app every Lowdefy phone app is built from (Expo SDK 58, React Native 0.88, Hermes, New
4
+ Architecture). It carries code only: the Lowdefy engine, `@lowdefy/client-native` and the shell's
5
+ plugin maps. Pages, menus and theme come from the app's Lowdefy server as data. No `ios/` or
6
+ `android/` folder is committed: Expo writes them at build time.
7
+
8
+ ## Config
9
+
10
+ `app.config.ts` reads `shell.json` from the path in `LOWDEFY_SHELL_CONFIG`, and falls back to
11
+ `shell.dev.json` (Lowdefy Dev):
12
+
13
+ ```json
14
+ {
15
+ "id": "field",
16
+ "name": "Field",
17
+ "profile": "field",
18
+ "version": "1.4.2",
19
+ "icon": "./icon.png",
20
+ "color": "#0B5FFF",
21
+ "scheme": "field",
22
+ "environment": "production",
23
+ "eas": { "owner": "example", "projectId": "2b6f…" },
24
+ "ios": { "bundleIdentifier": "com.example.field" },
25
+ "android": { "package": "com.example.field" },
26
+ "store": { "ios": "https://apps.apple.com/...", "android": "https://play.google.com/..." },
27
+ "app": { "id": "field", "name": "Field", "urls": { "production": "https://field.example.com" } }
28
+ }
29
+ ```
30
+
31
+ A shell opens exactly one app, named by `app`. Every app's `_js` runs in the shell's one JavaScript
32
+ realm, which cannot be sandboxed, so a second app in the same shell could reach the first one's
33
+ session token, storage and cache. `checkShellConfig.js` refuses a `shell.json` that lists `apps`,
34
+ names more than one app, or has no url for the shell's `environment`; `app.config.ts` runs it, so
35
+ `expo prebuild` and `expo export` fail on such a config.
36
+
37
+ `profile` names the shell's capability profile, one row of the table in `profiles.js`: `basic`,
38
+ `field`, `field-nfc`, `field-tracking`, and `dev` and `development` for Lowdefy Dev and development
39
+ builds. Each lists the native modules, config plugins, permissions, JavaScript entry modules,
40
+ required-reason APIs and collected data it carries, and may extend another (`field` is `basic` and
41
+ the camera). `basic`, and so every profile, carries on-device dictation (`expo-speech-recognition`)
42
+ with its microphone and speech purpose texts; `expo-camera` is given the same microphone text, so
43
+ plugin order cannot change it. `resolveProfile(name)` flattens one, and throws on a name the table
44
+ lacks. A shell
45
+ config without `profile` is `basic`, or `dev` for Lowdefy Dev. `app.config.ts` applies only the
46
+ profile's config plugins, and sets `capabilities` from it when `shell.json` has none.
47
+
48
+ The shell's version and type manifest are not in the Expo config: they are
49
+ `src/version/shellVersion.js` and `src/plugins/typeManifest.js`, part of the JavaScript bundle, so
50
+ they match the code that runs. An embedded launch reads the Expo config of the native build, so a
51
+ version there would keep a debug shell on Metro behind an app's `minShellVersion`, and a manifest
52
+ there would hide a type added since. The manifest lists every block, action and operator type the
53
+ shell registers (`src/plugins/types.js`, listed in
54
+ `src/plugins/shellPlugins.json`), with its plugin package and that package's major version.
55
+ `pnpm --filter=@lowdefy/shell type-manifest` rewrites it (`scripts/createTypeManifest.mjs`) after
56
+ a change to the plugin maps, `src/plugins/types.test.js` fails when it is stale, and
57
+ `pnpm release:version` rewrites it after the version bump.
58
+
59
+ ## Shell plugins
60
+
61
+ `plugins` in `shell.json` lists npm packages that add native code on top of the shell's profile:
62
+ `[{ name, version, typePrefix?, options? }]`, written by `lowdefy mobile config` from
63
+ `mobile.shells[].plugins`, which also installs them in the shell project. Each is a Lowdefy shell
64
+ plugin; `resolveShellPlugins.js` describes the package contract:
65
+
66
+ - `./shell` (CommonJS): a row shaped like one of `profiles.js` (`capabilities`, `modules`,
67
+ `plugins`, `permissions`, `privacyReasons`, `dataCollected`). `plugins` may be a function of the
68
+ plugin's `options`. `app.config.ts` loads it (`resolveShellPlugins.js`) and folds it into the
69
+ resolved profile (`applyShellPlugins.js`), so its config plugins, permissions, privacy reasons and
70
+ data join the profile's. Its modules are never excluded.
71
+ - `./native` (optional): named exports `blocks` and `actions`, `{ [typeName]: implementation }`.
72
+ The shell ships `src/plugins/appTypes.js` empty; `config` rewrites it in the copy to import each
73
+ plugin's `./native` with its `typePrefix` (`src/plugins/prefixTypes.js`), and `types.js`
74
+ registers them after the built-in types.
75
+ - `./types` (optional): `native: { blocks, actions, blockMetas, icons }`.
76
+ `scripts/createTypeManifest.mjs` adds `native.blocks` and `native.actions`, prefixed, to the type
77
+ manifest with the plugin's major version, and fails when two packages give one type name.
78
+ `config` runs it with `--write` in the copy, so the bundled `src/plugins/typeManifest.js` lists
79
+ the plugins' types.
80
+
81
+ `checkShellPlugins.js` refuses a plugin that links a module `@lowdefy/shell` carries itself
82
+ (`expo-camera`) or asks for a permission some profile carries and the shell's leaves out (the camera
83
+ on `basic`), naming the profile to pick. `config` runs it from the copied shell; `app.config.ts`
84
+ runs it again at prebuild. `fixtures/printer-plugin` is the test plugin.
85
+
86
+ ## Store uploads
87
+
88
+ `app.config.ts` sets what App Store Connect and Google Play would otherwise ask a person on upload:
89
+
90
+ - `ios.config.usesNonExemptEncryption: false`: the shell uses only the OS's encryption.
91
+ - `ios.privacyManifests`: the required-reason APIs of the profile's modules (`privacyReasons` in
92
+ `profiles.js`, where `basic` carries React Native's and expo-sqlite's), each listed once
93
+ (`getPrivacyManifest.js`).
94
+ - From `shell.json` `icon` (a 1024×1024 PNG, relative to `shell.json`) and `color`: the app icon,
95
+ the `expo-splash-screen` image on `color`, and the Android adaptive icon on `color`. Its
96
+ foreground, the icon scaled into the centre 66% of a transparent canvas, is written beside
97
+ `shell.json` as `adaptive-icon-<hash>.png` (`scripts/createShellAssets.mjs`) once per icon.
98
+ Without `icon` (Lowdefy Dev) Expo's defaults stay.
99
+
100
+ ## Versions
101
+
102
+ - `version` in `shell.json` is the store version (`major.minor.patch`), the app config's `version`.
103
+ Lowdefy Dev has none and builds as `0.0.0`.
104
+ - The shell version is the `@lowdefy/shell` package version, which the runtime sends as
105
+ `x-lowdefy-shell` and compares with `mobile.minShellVersion`. It is `src/version/shellVersion.js`,
106
+ which `pnpm --filter=@lowdefy/shell shell-version` rewrites (`scripts/createShellVersion.mjs`) and
107
+ `pnpm release:version` rewrites after the version bump; `src/version/shellVersion.test.js` fails
108
+ when it is stale.
109
+ - `runtimeVersion` is `<nativeVersion>-<profile>`, with `-<hash>` of the shell's `plugins` when it
110
+ lists any (`runtimeVersion.js`, a pure function `lowdefy mobile` reuses). Each plugin is keyed on
111
+ `name@version` and its `options` as JSON with keys sorted, so a new plugin version or option is a
112
+ new runtime version and a native build. An over-the-air update reaches only binaries with the same runtime version. Shell
113
+ identity is not part of it, so every shell on one `nativeVersion` and profile shares it.
114
+ - `nativeVersion` in `package.json` is bumped only when the shell's native dependencies or config
115
+ plugins change. `pnpm check:native-version` (run by the package's tests) computes Expo's
116
+ fingerprint of the shell's native inputs for every profile and fails when it differs from the one
117
+ `native-fingerprint.json` records for the current `nativeVersion`. After a bump,
118
+ `pnpm check:native-version --write` records the new one.
119
+ - With `eas`, the app config sets `owner`, `updates.url` (`https://u.expo.dev/<projectId>`) and
120
+ `extra.eas.projectId`.
121
+
122
+ ## Boot, cache and sync
123
+
124
+ - The shell opens its one app at once. There is no app picker.
125
+ - The last root config and pages the app showed are read from SQLite and rendered at once. The
126
+ cache is keyed by app, caller (person and organisation) and roles, from the server's
127
+ `x-lowdefy-caller` stamp.
128
+ - In the background the shell fetches `GET /api/mobile/root` with `If-None-Match`, then every page
129
+ whose index hash changed, 4 at a time. Dynamic pages, and dev pages (no hash), are fetched on
130
+ each visit.
131
+ - Every call carries `x-lowdefy-shell` (the shell's version) and the session token as a bearer
132
+ token, kept in the Keychain or Keystore.
133
+
134
+ ## Sign-in, unlock and sign-out
135
+
136
+ `src/auth`. The mobile root carries the app's `mobile.auth` (null for an app without auth, which
137
+ opens with no sign-in screen).
138
+
139
+ - With nobody signed in, the native email-code screen asks for an email, then the 6-digit code
140
+ (iOS offers it from Mail). It always says "If this email can sign in, a code is on its way".
141
+ "Other ways to sign in", `emailCode: false`, a server without email codes and a second factor
142
+ open the app's sign-in page in an ephemeral system sheet (`/auth/mobile`, with a state, a PKCE
143
+ S256 challenge and the platform). Once signed in, the page asks "Sign in to <app> on this
144
+ device?"; Continue creates a new session for the phone and returns a one-time token for it. iOS
145
+ returns to the shell's scheme; Android returns by the verified app link
146
+ `<app url>/auth/mobile/callback`, which `app.config.ts` declares for each app url
147
+ (`authCallbackIntentFilters.js`) and the app's `assetlinks.json` names the shell for. The shell
148
+ checks the state and exchanges the one-time token with the code verifier at
149
+ `/api/auth/one-time-token/verify`.
150
+ - The session token is kept in `expo-secure-store` on this device only: readable while unlocked,
151
+ behind biometrics with `unlock: biometric`, or readable after first unlock for the
152
+ `field-tracking` profile. It is read once per unlock and held in memory. Calls send no cookies.
153
+ - `unlock: biometric` shows a lock screen on launch and after `unlockAfter` seconds in the
154
+ background, with "Sign in again" as the way out.
155
+ - Sign-out, the token cleared by the core (a Logout action, a 401), and a call with the token
156
+ answered as the signed-out caller remove the token, every cached root and page of the app and
157
+ the person's storage keys, then open the app afresh. A 403 drops that caller's cache.
158
+ `sharedDevice: true` signs out whenever the app goes to the background.
159
+
160
+ ## Version skew
161
+
162
+ - A page whose type set names a type the shell lacks, or carries at another major version, shows
163
+ "Update the app to open this page" with the store link. Other pages still open.
164
+ - `mobile.minShellVersion` above the shell's version shows the same screen for every page.
165
+ - A page request refused with a 409 for another build syncs the root and changed pages, then is
166
+ retried once when its own page did not change; otherwise the screen is rebuilt on the new page.
167
+ - A response answered as another caller drops the screens, syncs under the new caller's key and
168
+ opens home.
169
+
170
+ ## Navigation
171
+
172
+ Expo Router, with one route for every page, `app/(t0,t1,t2,t3,t4)/[...path].js`, in five route
173
+ groups: with `mobile.menus.tabs` each tab is a group with its own native stack, under native tabs.
174
+ A `drawer` frame is one stack with a menu button; a `stack` frame is one stack. `Link` actions go
175
+ through the router; a link to a tab's page switches tab (with its query), and links to other sites
176
+ open in the system browser.
177
+
178
+ A `Screen` page reports its header and `src/navigation/getStackOptions.js` maps it to native stack
179
+ options: the title, or an iOS large title (`largeTitle`) that collapses as the page scrolls; the
180
+ presentation; a native search bar (`headerSearch`, focused on each show with `autoFocus`); and
181
+ the `headerLeft`/`headerRight` slots and `headerActions` at their natural size. On iOS each action
182
+ is a native bar button drawing its SF Symbol in the bar's glass, and an icon with no symbol (or
183
+ the slot) a custom item; elsewhere a side is a row of icon buttons. A side with nothing keeps the
184
+ stack's own (a drawer's menu button). Pull-to-refresh (`onRefresh`) and `onFocus` come from the
185
+ page's screen: `PageScreen` passes `useIsFocused()`.
186
+
187
+ Before a page is first drawn, `PageScreen` sets the bar its config gives
188
+ (`navigation/getInitialScreenOptions.js`: a literal `largeTitle`, `headerSearch`, `title`,
189
+ `presentation` and `backgroundTint`) and waits a frame: iOS keeps a large title collapsed when
190
+ the page's scroll view was first laid out under a standard bar, and the Screen reports its options
191
+ only once it renders inside that scroll view.
192
+
193
+ A Screen's `headerBottom` slot pins its blocks under the bar. `PageScreen` passes `headerInset`,
194
+ where the bar's bottom edge is in the page's scroll view: the native stack's
195
+ `useAnimatedHeaderHeight()` (from `expo-router/native-stack`), which follows a large title as it
196
+ collapses, when the page scrolls under the bar (`navigation/isPageUnderHeader.js`: iOS 26 and
197
+ later, or a large title or search bar before that), and 0 when the page sits below it (Android).
198
+ A page with a headerBottom hides the bar's own hairline; the strip draws one under itself.
199
+ A Screen with a `backgroundTint` gets a clear bar on iOS (`headerTransparent`; before iOS 26 also
200
+ clear at the scroll edge, with the chrome material once content scrolls under it), so the tint the
201
+ page draws at the screen's top shows behind the bar, and the page counts as under the bar.
202
+
203
+ Each screen keeps its page state per route instance (`useRoute().key`), so task B pushed on task A
204
+ has its own, and reads `_url_query` from its own route (`routePath`). The focused screen sets the
205
+ session's current path and instance (`setCurrentPath`), which the host's `location` and Lowdefy
206
+ Dev's shown page (`getShownPage`, `shownScreen` in the shell's globals) read.
207
+
208
+ Tabs (`mobile.menus.tabs`) draw their icons in the native tab bar, the selected tab in the theme's
209
+ `colorPrimary`. On iOS a tab draws an SF Symbol: its `sfSymbol`, or the symbol client-native's
210
+ `getSfSymbol` maps from its icon (`Inbox` to `tray`, `FileText` to `doc.text`, `edit` to `pencil`).
211
+ Elsewhere, and for an icon with no symbol, `TabIconImages` draws the Lucide icon from the item's
212
+ icon data once, out of sight, and reads it back as a PNG for the tab bar to tint. A tab with
213
+ `role: search` is iOS 26's search tab, apart at the right of the bar with a magnifier; on Android
214
+ and older iOS it is a tab with the search icon. Its page is a Screen with
215
+ `headerSearch: { autoFocus: true }`.
216
+
217
+ ## Plugins, `_js`, locale and theme
218
+
219
+ - Pages run every actions-core action and the client operators of every default operator package
220
+ (`src/plugins/types.js`). The actions whose web bodies reach the DOM run native bodies under the
221
+ same names (`src/actions`): `SetFocus` focuses the control a block registered, `ScrollTo`
222
+ scrolls the page's ScrollView to a block's measured place, `CopyToClipboard` uses
223
+ `expo-clipboard`, and the local storage actions keep the app's keys in the device store, from
224
+ which sign-out clears the user keys. `SetDarkMode` and `SetLocale` keep their actions-core
225
+ bodies: they call the window members the theme provider and the shell's locale set.
226
+ - `_js` reaches the shell as module text: a page's `_jsEntries`, and the root's for the app
227
+ events. `src/js/evaluateJsEntries.js` is the one place it becomes functions, with
228
+ `new Function`.
229
+ - The app's locale is chosen as on the web: the `SetLocale` choice the device keeps, the device's
230
+ locale the app supports, the app's default. Every dayjs locale is bundled
231
+ (`src/locale/dayjsLocales.js`, written by `scripts/createDayjsLocales.mjs`) and loaded when
232
+ used.
233
+ - One theme provider holds the theme mode for every screen, from the root's `theme` and the
234
+ `SetDarkMode` choice; the root's `classMap`, with a dev page's own `_classMap`, styles classes.
235
+ - Block icons are drawn from the icon data each page carries (`_icons`), which `PageScreen` adds
236
+ to the icon map client-native's `components.Icon` reads (`types.icons`).
237
+ - Menu icons are drawn from the icon data the root carries: with client-native's `Icon` in the
238
+ drawer menu, and as SF Symbols or images in the tab bar (see Navigation).
239
+ - Hermes has no `crypto.getRandomValues`, which `_uuid` needs; `expo-crypto` fills it.
240
+
241
+ ## Lowdefy Dev
242
+
243
+ `src/dev`. Lowdefy Dev is this shell built as the `dev` variant: `LOWDEFY_SHELL_VARIANT=dev | release`
244
+ (`shellVariant.js`), `dev` by default for Lowdefy Dev (no `LOWDEFY_SHELL_CONFIG`) and `release` for
245
+ every other shell. A release bundle resolves `src/dev/index.js` to an empty module
246
+ (`metro.config.js`), so a customer shell carries none of it. Every shell
247
+ leaves out the native modules its profile does not carry, and a release shell also leaves out
248
+ Lowdefy Dev's own (`expo-sensors`, `react-native-view-shot`, `expo-dev-client`) whatever its profile
249
+ (`getExclusions` in `shellVariant.js`): a `basic` shell links no `expo-camera`. `app.config.ts`
250
+ leaves out their config plugins and blocks their Android permissions, `withExcludedNativeModules.js`
251
+ passes them as exclusions to Expo autolinking in the generated Podfile and settings.gradle, and
252
+ `metro.config.js` resolves the left-out profiles' JavaScript entries to empty modules.
253
+
254
+ `pnpm check:release-bundle` checks a release shell (Lowdefy Dev's, or the one
255
+ `LOWDEFY_SHELL_CONFIG` names) on each release profile (`--profile` names one) and both platforms:
256
+ its bundle holds neither the dev-tab client's marker string nor a left-out module's native name,
257
+ and an `expo prebuild` of it links none of the left-out modules and asks for none of their
258
+ permissions. `--control` first checks that the `dev` variant holds and links them.
259
+
260
+ - **Connect.** `lowdefy-dev://connect?url=<server>&pair=<token>` (the QR code `lowdefy dev --lan`
261
+ prints, or the link `--ios`/`--android` open), a scanned code (`expo-camera`), a typed address, or
262
+ one of the last 5 servers. A server not on this device is confirmed first ("Connect to
263
+ `<host>`?"); a simulator's or USB phone's localhost connects at once. Lowdefy Dev opens one dev
264
+ server at a time; switching server closes the open app. Each server is its own app, with its own
265
+ cache and session, and every call to it (fetch and websocket) carries its pairing
266
+ token as `x-lowdefy-pair`.
267
+ - **Shake menu** (or `lowdefy-dev://menu`): reload, switch server, clear data, show logs, the block
268
+ gallery, and the shell version.
269
+ - **Dev-tab client.** The open app joins the dev server's tab channel as a browser tab does: it
270
+ registers on `/api/reload` (an event stream read from `expo/fetch`, or polls of
271
+ `/api/reload?transport=poll` every second when the stream cannot be read), posts the page it shows
272
+ to `/api/dev-inspect/page`, and answers on `/api/dev-inspect`:
273
+ - `reload`: refetches the root and the page on screen and rebuilds the screens with empty page
274
+ state, staying on the page.
275
+ - `inspect-request`, `eval-request`: the browser tab's payloads (shared fixtures with the dev
276
+ server's tests).
277
+ - `step-request` `{ step }`: a journey step by block id (`click`, `fill`, `select`,
278
+ `press: 'Enter'`, `wait`, `screenshot`), run through the engine; it waits for the action chain
279
+ to settle. A screenshot is the root view as a base64 PNG (`react-native-view-shot`).
280
+ - `inject` `{ inject: { kind, value, name } }`: fakes the next device result
281
+ (`src/dev/inject`): `scan` `{ data, type }`, `photo` `{ base64, ... }` and `nfc` for the next
282
+ read; `transcript` `{ partials?, final }` or `{ error }` for the next dictation session, which
283
+ plays the partials and the final a moment apart, while a session with nothing injected uses
284
+ the microphone (blocks-native's `installDictationSource`, which Lowdefy Dev fills with
285
+ `injected`; `permission` `microphone` answers its prompt);
286
+ `location` `{ latitude, longitude, ... }`, or a route as a list, read one at a time with
287
+ the last one staying; `permission` with `name` and `'granted' | 'denied'`, standing until
288
+ changed; `network` `'offline' | 'online'`, which fails the app's every fetch as offline (the tab
289
+ client keeps its own) and takes the shell's online state offline (`src/host/networkStatus.js`,
290
+ which expo-network also feeds), so the NoConnection banner shows and the app's `offline` events
291
+ fire; `clear`. `push` is refused until the shell carries expo-notifications.
292
+ Device actions read these with `injected(name)` (`src/device/injected.js`), which returns
293
+ nothing in a release shell; `GeolocationCurrentPosition` answers with an injected location.
294
+ - **Recorder** (`src/dev/recorder`). What the developer does is recorded as the web recorder records
295
+ it: the engine trace's events paired with the presses and changes client-native reports by block
296
+ id, a pageview when the page on screen changes, password inputs redacted, each record labelled
297
+ `device: ios | android` and `shell_version`, posted to `/api/dev-recording`. `lowdefy journeys
298
+ session` lists and prints phone sessions as it does browser ones.
299
+
300
+ ## Checks
301
+
302
+ ```bash
303
+ pnpm --filter=@lowdefy/shell test # Jest, and the hello fixture build
304
+ pnpm --filter=@lowdefy/shell export:ios # Metro and Hermes bundle, no simulator needed
305
+ pnpm --filter=@lowdefy/shell export:android
306
+ pnpm --filter=@lowdefy/shell check:release-bundle # each release profile links only its modules
307
+ pnpm --filter=@lowdefy/shell check:native-version # native code changed only with a nativeVersion bump
308
+ ```
@@ -0,0 +1,38 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import React from 'react';
18
+ import { useLocalSearchParams } from 'expo-router';
19
+ import { serializeQuery } from '@lowdefy/client-core';
20
+
21
+ import getTabHref from '../../src/navigation/getTabHref.js';
22
+ import PageScreen from '../../src/components/PageScreen.js';
23
+ import SwitchToTab from '../../src/components/SwitchToTab.js';
24
+ import { useSessionState } from '../../src/ShellContext.js';
25
+
26
+ // Every Lowdefy page, by path. The page and its path params come from the page index; the route's
27
+ // other params are the page's query. A tab's own page is shown on its tab, not stacked here.
28
+ export default function PathRoute() {
29
+ const { path, ...query } = useLocalSearchParams();
30
+ const { root } = useSessionState();
31
+ const segments = Array.isArray(path) ? path : [path];
32
+ const pagePath = segments.join('/');
33
+ const tabHref = getTabHref({ path: pagePath, query: serializeQuery(query), root });
34
+ if (tabHref !== null) {
35
+ return <SwitchToTab href={tabHref} />;
36
+ }
37
+ return <PageScreen path={pagePath} query={serializeQuery(query)} />;
38
+ }
@@ -0,0 +1,48 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import React from 'react';
18
+ import { Stack } from 'expo-router';
19
+
20
+ import getFrame from '../../src/navigation/getFrame.js';
21
+ import { getSlotIndex } from '../../src/navigation/frameSlots.js';
22
+ import MenuButton from '../../src/components/MenuButton.js';
23
+ import SlotContext from '../../src/navigation/SlotContext.js';
24
+ import { useSessionState } from '../../src/ShellContext.js';
25
+
26
+ // A deep link opens on top of the group's first screen, so back leads to it.
27
+ export const unstable_settings = { initialRouteName: 'index' };
28
+
29
+ // The stack of one frame group (a tab, or the app's one stack). A drawer frame puts its menu
30
+ // button in the header of the group's first screen. Expo Router passes the group's own segment,
31
+ // such as `(t1)`, to a layout shared by several groups; its screens read the group from SlotContext.
32
+ export default function GroupLayout({ segment }) {
33
+ const { root } = useSessionState();
34
+ const frame = getFrame(root.menus);
35
+ return (
36
+ <SlotContext.Provider value={getSlotIndex(segment)}>
37
+ <Stack screenOptions={{ title: '' }}>
38
+ <Stack.Screen
39
+ name="index"
40
+ options={
41
+ frame.kind === 'drawer' ? { headerLeft: () => <MenuButton items={frame.items} /> } : {}
42
+ }
43
+ />
44
+ <Stack.Screen name="[...path]" />
45
+ </Stack>
46
+ </SlotContext.Provider>
47
+ );
48
+ }
@@ -0,0 +1,38 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import React, { useContext } from 'react';
18
+ import { useLocalSearchParams } from 'expo-router';
19
+ import { serializeQuery } from '@lowdefy/client-core';
20
+
21
+ import getSlotRootPath from '../../src/navigation/getSlotRootPath.js';
22
+ import MessageScreen from '../../src/components/MessageScreen.js';
23
+ import PageScreen from '../../src/components/PageScreen.js';
24
+ import SlotContext from '../../src/navigation/SlotContext.js';
25
+ import { useSessionState } from '../../src/ShellContext.js';
26
+
27
+ // The first screen of a frame group: its tab's page, or the app's home page. A link to a tab's
28
+ // page switches to the tab (toRouterHref) with its query as the route's params.
29
+ export default function GroupIndex() {
30
+ const slotIndex = useContext(SlotContext);
31
+ const query = useLocalSearchParams();
32
+ const { root } = useSessionState();
33
+ const path = getSlotRootPath({ root, slotIndex });
34
+ if (path === null) {
35
+ return <MessageScreen testID="shell-no-home" title="This app has no page to open" />;
36
+ }
37
+ return <PageScreen path={path} query={serializeQuery(query)} />;
38
+ }