@terreno/mcp 57.21.0 → 58.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +57 -3
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +17 -16
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
  7. package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
  8. package/dist/docs/prompts/debug.md +6 -4
  9. package/dist/docs/prompts/screen.md +11 -17
  10. package/dist/docs/resources/overview.md +5 -3
  11. package/dist/docs/resources/patterns.md +157 -238
  12. package/dist/docs/resources/syncdb.md +44 -16
  13. package/dist/docs/templates/bootstrap/CLAUDE.template.md +2 -2
  14. package/dist/docs/templates/bootstrap/cursorrules.template.md +7 -5
  15. package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
  16. package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
  17. package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
  18. package/dist/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
  19. package/dist/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
  20. package/dist/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
  21. package/dist/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
  22. package/dist/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
  23. package/dist/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
  24. package/dist/docs/upgrades/58.0.0-beta.1.md +504 -0
  25. package/dist/docs/upgrades/58.0.0.md +495 -4
  26. package/dist/docs/versioned/next/README.md +2 -2
  27. package/dist/docs/versioned/next/approval-rubric.md +2 -2
  28. package/dist/docs/versioned/next/explanation/admin-interface.md +6 -5
  29. package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
  30. package/dist/docs/versioned/next/explanation/ai-workflows.md +5 -3
  31. package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
  32. package/dist/docs/versioned/next/explanation/authentication.md +62 -43
  33. package/dist/docs/versioned/next/explanation/dependency-management.md +1 -1
  34. package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  35. package/dist/docs/versioned/next/explanation/example-coverage.md +2 -2
  36. package/dist/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
  37. package/dist/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
  38. package/dist/docs/versioned/next/explanation/local-first-data.md +11 -9
  39. package/dist/docs/versioned/next/explanation/model-router-actions.md +1 -1
  40. package/dist/docs/versioned/next/explanation/modular-api-design.md +2 -2
  41. package/dist/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
  42. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
  43. package/dist/docs/versioned/next/explanation/source-rules.md +2 -2
  44. package/dist/docs/versioned/next/explanation/typescript-configuration.md +2 -4
  45. package/dist/docs/versioned/next/explanation/versioning-policy.md +3 -3
  46. package/dist/docs/versioned/next/how-to/README.md +3 -2
  47. package/dist/docs/versioned/next/how-to/add-feature-flags.md +50 -25
  48. package/dist/docs/versioned/next/how-to/add-organizations.md +2 -1
  49. package/dist/docs/versioned/next/how-to/admin-custom-home.md +8 -2
  50. package/dist/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
  51. package/dist/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
  52. package/dist/docs/versioned/next/how-to/build-admin-screens.md +10 -8
  53. package/dist/docs/versioned/next/how-to/build-for-web.md +1 -1
  54. package/dist/docs/versioned/next/how-to/call-external-apis.md +2 -2
  55. package/dist/docs/versioned/next/how-to/circleci.md +12 -6
  56. package/dist/docs/versioned/next/how-to/configure-better-auth.md +114 -60
  57. package/dist/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
  58. package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
  59. package/dist/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
  60. package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
  61. package/dist/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
  62. package/dist/docs/versioned/next/how-to/github-actions-ci.md +2 -2
  63. package/dist/docs/versioned/next/how-to/in-app-notifications.md +1 -1
  64. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
  65. package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
  66. package/dist/docs/versioned/next/how-to/product-announcements.md +6 -3
  67. package/dist/docs/versioned/next/how-to/rate-limiting.md +38 -16
  68. package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
  69. package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -3
  70. package/dist/docs/versioned/next/how-to/set-up-social-login.md +1 -1
  71. package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
  72. package/dist/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
  73. package/dist/docs/versioned/next/how-to/upgrade-banner.md +5 -3
  74. package/dist/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
  75. package/dist/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
  76. package/dist/docs/versioned/next/how-to/websocket-integration.md +113 -402
  77. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
  78. package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
  79. package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
  80. package/dist/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
  81. package/dist/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
  82. package/dist/docs/versioned/next/reference/README.md +1 -1
  83. package/dist/docs/versioned/next/reference/admin-frontend.md +85 -70
  84. package/dist/docs/versioned/next/reference/admin-spa.md +10 -4
  85. package/dist/docs/versioned/next/reference/ai.md +36 -4
  86. package/dist/docs/versioned/next/reference/announcements.md +3 -3
  87. package/dist/docs/versioned/next/reference/api.md +39 -24
  88. package/dist/docs/versioned/next/reference/cli.md +3 -4
  89. package/dist/docs/versioned/next/reference/environment-variables.md +16 -16
  90. package/dist/docs/versioned/next/reference/feature-flags.md +48 -7
  91. package/dist/docs/versioned/next/reference/jobs.md +2 -2
  92. package/dist/docs/versioned/next/reference/legacy/rtk.md +25 -600
  93. package/dist/docs/versioned/next/reference/mcp-server.md +441 -1158
  94. package/dist/docs/versioned/next/reference/syncdb.md +426 -16
  95. package/dist/docs/versioned/next/reference/test.md +28 -3
  96. package/dist/docs/versioned/next/reference/ui.md +132 -19
  97. package/dist/docs/versioned/next/tasks/native-module-baseline.md +8 -4
  98. package/dist/docs/versioned/next/tasks/rate-limiting.md +9 -2
  99. package/dist/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
  100. package/dist/docs/versioned/next/tasks/remove-rtk.md +155 -0
  101. package/dist/local/localTools.d.ts.map +1 -1
  102. package/dist/local/localTools.js +3 -54
  103. package/dist/local/localTools.js.map +1 -1
  104. package/dist/local/tools/runtime.d.ts +0 -21
  105. package/dist/local/tools/runtime.d.ts.map +1 -1
  106. package/dist/local/tools/runtime.js +0 -176
  107. package/dist/local/tools/runtime.js.map +1 -1
  108. package/dist/local/tools/syncDb.d.ts +0 -11
  109. package/dist/local/tools/syncDb.d.ts.map +1 -1
  110. package/dist/local/tools/syncDb.js +1 -5
  111. package/dist/local/tools/syncDb.js.map +1 -1
  112. package/dist/packageGuidelines.d.ts +3 -2
  113. package/dist/packageGuidelines.d.ts.map +1 -1
  114. package/dist/packageGuidelines.js +4 -11
  115. package/dist/packageGuidelines.js.map +1 -1
  116. package/dist/prompts.d.ts.map +1 -1
  117. package/dist/prompts.js +17 -18
  118. package/dist/prompts.js.map +1 -1
  119. package/dist/resources.d.ts.map +1 -1
  120. package/dist/resources.js +1 -8
  121. package/dist/resources.js.map +1 -1
  122. package/dist/search/chunker.d.ts +1 -1
  123. package/dist/search/chunker.d.ts.map +1 -1
  124. package/dist/search/inferPackages.d.ts +1 -1
  125. package/dist/search/inferPackages.js +5 -5
  126. package/dist/search/inferPackages.js.map +1 -1
  127. package/dist/tools.d.ts +6 -0
  128. package/dist/tools.d.ts.map +1 -1
  129. package/dist/tools.js +126 -119
  130. package/dist/tools.js.map +1 -1
  131. package/package.json +6 -6
  132. package/src/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
  133. package/src/docs/guidelines/ui/guidelines/core.md +1 -1
  134. package/src/docs/prompts/debug.md +6 -4
  135. package/src/docs/prompts/screen.md +11 -17
  136. package/src/docs/resources/overview.md +5 -3
  137. package/src/docs/resources/patterns.md +157 -238
  138. package/src/docs/resources/syncdb.md +44 -16
  139. package/src/docs/templates/bootstrap/CLAUDE.template.md +2 -2
  140. package/src/docs/templates/bootstrap/cursorrules.template.md +7 -5
  141. package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
  142. package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
  143. package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
  144. package/src/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
  145. package/src/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
  146. package/src/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
  147. package/src/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
  148. package/src/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
  149. package/src/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
  150. package/src/docs/upgrades/58.0.0-beta.1.md +504 -0
  151. package/src/docs/upgrades/58.0.0.md +495 -4
  152. package/src/docs/versioned/next/README.md +2 -2
  153. package/src/docs/versioned/next/approval-rubric.md +2 -2
  154. package/src/docs/versioned/next/explanation/admin-interface.md +6 -5
  155. package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
  156. package/src/docs/versioned/next/explanation/ai-workflows.md +5 -3
  157. package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
  158. package/src/docs/versioned/next/explanation/authentication.md +62 -43
  159. package/src/docs/versioned/next/explanation/dependency-management.md +1 -1
  160. package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  161. package/src/docs/versioned/next/explanation/example-coverage.md +2 -2
  162. package/src/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
  163. package/src/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
  164. package/src/docs/versioned/next/explanation/local-first-data.md +11 -9
  165. package/src/docs/versioned/next/explanation/model-router-actions.md +1 -1
  166. package/src/docs/versioned/next/explanation/modular-api-design.md +2 -2
  167. package/src/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
  168. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
  169. package/src/docs/versioned/next/explanation/source-rules.md +2 -2
  170. package/src/docs/versioned/next/explanation/typescript-configuration.md +2 -4
  171. package/src/docs/versioned/next/explanation/versioning-policy.md +3 -3
  172. package/src/docs/versioned/next/how-to/README.md +3 -2
  173. package/src/docs/versioned/next/how-to/add-feature-flags.md +50 -25
  174. package/src/docs/versioned/next/how-to/add-organizations.md +2 -1
  175. package/src/docs/versioned/next/how-to/admin-custom-home.md +8 -2
  176. package/src/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
  177. package/src/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
  178. package/src/docs/versioned/next/how-to/build-admin-screens.md +10 -8
  179. package/src/docs/versioned/next/how-to/build-for-web.md +1 -1
  180. package/src/docs/versioned/next/how-to/call-external-apis.md +2 -2
  181. package/src/docs/versioned/next/how-to/circleci.md +12 -6
  182. package/src/docs/versioned/next/how-to/configure-better-auth.md +114 -60
  183. package/src/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
  184. package/src/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
  185. package/src/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
  186. package/src/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
  187. package/src/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
  188. package/src/docs/versioned/next/how-to/github-actions-ci.md +2 -2
  189. package/src/docs/versioned/next/how-to/in-app-notifications.md +1 -1
  190. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
  191. package/src/docs/versioned/next/how-to/password-reset.md +1 -1
  192. package/src/docs/versioned/next/how-to/product-announcements.md +6 -3
  193. package/src/docs/versioned/next/how-to/rate-limiting.md +38 -16
  194. package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
  195. package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -3
  196. package/src/docs/versioned/next/how-to/set-up-social-login.md +1 -1
  197. package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
  198. package/src/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
  199. package/src/docs/versioned/next/how-to/upgrade-banner.md +5 -3
  200. package/src/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
  201. package/src/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
  202. package/src/docs/versioned/next/how-to/websocket-integration.md +113 -402
  203. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
  204. package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
  205. package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
  206. package/src/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
  207. package/src/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
  208. package/src/docs/versioned/next/reference/README.md +1 -1
  209. package/src/docs/versioned/next/reference/admin-frontend.md +85 -70
  210. package/src/docs/versioned/next/reference/admin-spa.md +10 -4
  211. package/src/docs/versioned/next/reference/ai.md +36 -4
  212. package/src/docs/versioned/next/reference/announcements.md +3 -3
  213. package/src/docs/versioned/next/reference/api.md +39 -24
  214. package/src/docs/versioned/next/reference/cli.md +3 -4
  215. package/src/docs/versioned/next/reference/environment-variables.md +16 -16
  216. package/src/docs/versioned/next/reference/feature-flags.md +48 -7
  217. package/src/docs/versioned/next/reference/jobs.md +2 -2
  218. package/src/docs/versioned/next/reference/legacy/rtk.md +25 -600
  219. package/src/docs/versioned/next/reference/mcp-server.md +441 -1158
  220. package/src/docs/versioned/next/reference/syncdb.md +426 -16
  221. package/src/docs/versioned/next/reference/test.md +28 -3
  222. package/src/docs/versioned/next/reference/ui.md +132 -19
  223. package/src/docs/versioned/next/tasks/native-module-baseline.md +8 -4
  224. package/src/docs/versioned/next/tasks/rate-limiting.md +9 -2
  225. package/src/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
  226. package/src/docs/versioned/next/tasks/remove-rtk.md +155 -0
  227. package/dist/docs/guidelines/rtk/guidelines/core.md +0 -26
  228. package/dist/docs/resources/rtk.md +0 -181
  229. package/dist/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
  230. package/dist/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
  231. package/dist/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
  232. package/dist/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
  233. package/dist/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
  234. package/dist/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
  235. package/dist/docs/templates/bootstrap/frontend/store/sdk.template.md +0 -80
  236. package/src/docs/guidelines/rtk/guidelines/core.md +0 -26
  237. package/src/docs/resources/rtk.md +0 -181
  238. package/src/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
  239. package/src/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
  240. package/src/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
  241. package/src/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
  242. package/src/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
  243. package/src/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
  244. package/src/docs/templates/bootstrap/frontend/store/sdk.template.md +0 -80
@@ -1,6 +1,105 @@
1
- # 58.0.0 (stub)
1
+ # Upgrading to 58.0.0
2
2
 
3
- This file is a placeholder until Terreno 58 is cut. When that release ships, expand this page into a full upgrade note.
3
+ **Action required:** Yes
4
+ **Affected packages:** @terreno/rtk (removed), @terreno/syncdb, @terreno/ui, @terreno/admin-frontend, @terreno/admin-spa, @terreno/api, @terreno/ai, @terreno/feature-flags, @terreno/mcp, @terreno/cli, create-terreno-app
5
+
6
+ ## Summary
7
+
8
+ First release of the Expo SDK 58 line. Upgrade the app to Expo 58 and React Native 0.88, rebuild native binaries, then bump every `@terreno/*` package to `58.0.0` together. This major also deletes `@terreno/rtk` and Redux: `@terreno/syncdb` is the only frontend data, request, and session layer. Run the `GptHistory` and `McpServiceToken` data migrations before deploying 58 frontends. Each removal has its own section below.
9
+
10
+ Order of work:
11
+
12
+ 1. Upgrade Expo (next section) and rebuild native binaries.
13
+ 2. Backend: remove `modelRouter` `realtime`, then run the [MCP service token](#mcp-service-tokens-are-a-synced-collection) and [GPT history](#gpt-histories-are-a-synced-collection) migrations.
14
+ 3. Frontend: [replace `@terreno/rtk` and Redux](#terrenortk-and-redux-are-removed) with `@terreno/syncdb`, then update [admin](#admin-no-longer-takes-an-rtk-api) and [ui navigators](#consent-and-announcement-navigators-take-a-client).
15
+
16
+ ## Upgrade Expo before Terreno
17
+
18
+ | Package | Version |
19
+ |---------|---------|
20
+ | `expo` | `~58.0.14` |
21
+ | `react-native` | `0.88.0` |
22
+ | `react` / `react-dom` | `19.3.0` |
23
+ | `@types/react` | `~19.3.0` |
24
+ | `@sentry/react-native` | `~8.28.0` (major 7 → 8) |
25
+ | `react-native-gesture-handler` | `~3.3.0` (major 2 → 3) |
26
+ | `react-native-webview` | `14.0.1` (major 13 → 14) |
27
+
28
+ Run `bunx expo install expo@^58.0.0 --fix` at the app root, rebuild dev clients and store binaries (the native fingerprint changes), then `bun run compile` / `bun run lint`.
29
+
30
+ ### Expo Router `initialRouteName`
31
+
32
+ **What changed**
33
+
34
+ Expo Router 58 no longer accepts `initialRouteName` as a `<Slot>` / `<Stack>` / `<Navigator>` prop. `SidebarNavigation` ignores it.
35
+
36
+ **Migration**
37
+
38
+ ```tsx
39
+ // Before
40
+ <Slot initialRouteName="home" />
41
+
42
+ // After — in the same _layout.tsx
43
+ export const unstable_settings = {initialRouteName: "home"};
44
+ <Slot />
45
+ ```
46
+
47
+ ### Custom `app/+html.tsx`
48
+
49
+ Expo Router 58 renders static web pages by streaming. The bundle `<script>` tags,
50
+ React Native Web styles, and route metadata now reach a custom `app/+html.tsx` through
51
+ `useServerDocumentContext()`. A `+html.tsx` that does not render them exports pages with
52
+ no JavaScript: the static HTML shows, but nothing hydrates or responds.
53
+
54
+ ```tsx
55
+ import {ScrollViewStyleReset, useServerDocumentContext} from "expo-router/html";
56
+
57
+ export default function Root({children}: {children: React.ReactNode}) {
58
+ const {bodyAttributes, bodyNodes, headNodes, htmlAttributes} = useServerDocumentContext();
59
+ return (
60
+ <html lang="en" {...htmlAttributes}>
61
+ <head>
62
+ <meta charSet="utf-8" />
63
+ <ScrollViewStyleReset />
64
+ {headNodes}
65
+ </head>
66
+ <body {...bodyAttributes}>
67
+ {children}
68
+ {bodyNodes}
69
+ </body>
70
+ </html>
71
+ );
72
+ }
73
+ ```
74
+
75
+ Apps without a custom `+html.tsx` need no change.
76
+
77
+ ### React Native 0.88 instance types
78
+
79
+ **What changed**
80
+
81
+ React Native 0.87+ ships generated types without `react-native-legacy-deep-imports`. Component names such as `View`, `Text`, `ScrollView`, and `TextInput` are no longer usable as ref types. `Box.scrollRef`, `TextField.inputRef`, and the `Text` `ref` now use the instance types.
82
+
83
+ **Migration**
84
+
85
+ ```tsx
86
+ // Before
87
+ const ref = useRef<TextInput>(null);
88
+
89
+ // After
90
+ import type {TextInputInstance} from "react-native";
91
+ const ref = useRef<TextInputInstance>(null);
92
+ ```
93
+
94
+ Use `ViewInstance`, `TextInstance`, `ScrollViewInstance`, and `TextInputInstance`. `TextStyle` is `Readonly`; build style objects instead of mutating them. `TextInput.State.currentlyFocusedInput()` replaces `currentlyFocusedField()`.
95
+
96
+ ### `@terreno/ui` peer dependency
97
+
98
+ `@terreno/ui` peers `react-native` `0.88.0`. Installing `@terreno/ui@58` next to Expo 57 is unsupported.
99
+
100
+ ### `@sentry/react-native` 8
101
+
102
+ Expo 58 bundles `@sentry/react-native` `~8.28.0`. Follow Sentry's 7 → 8 migration guide for your app's `Sentry.init` options.
4
103
 
5
104
  ## `Filter` and `FilterProps` are removed
6
105
 
@@ -8,6 +107,398 @@ This file is a placeholder until Terreno 58 is cut. When that release ships, exp
8
107
 
9
108
  ## HTTP rate limiting defaults on
10
109
 
11
- `TerrenoApp` will enable HTTP rate limiting by default (memory store unless you pass `store`). Apps that must stay unlimited must pass an explicit off once 58 defines that API.
110
+ `TerrenoApp` now mounts the HTTP rate limiter when `rateLimit` is omitted: memory store, auth 20 / 15 min (login, signup, refresh, GitHub OAuth, Better Auth credential routes), api 600 / 15 min (everything else). `/health`, `/healthz`, `/openapi.json`, and `/swagger` are never limited. Over the limit returns 429 `rate-limit-exceeded` with `Retry-After`.
111
+
112
+ Steps:
113
+
114
+ 1. **Cloud Run (or any load balancer): pass `trustProxy: 1`.** Without it every unauthenticated request keys by the front end's address, so the whole service shares one auth bucket (20 logins per 15 minutes for all users).
115
+
116
+ ```typescript
117
+ new TerrenoApp({userModel: User, rateLimit: {trustProxy: 1}});
118
+ ```
119
+
120
+ 2. **If you set `app.set("trust proxy", …)` before `build()`** (for example in `beforeJsonSetup`), move the value to `rateLimit.trustProxy`. The limiter sets Express `trust proxy` when it mounts and defaults it to `false`.
121
+ 3. **More than one replica:** the memory store is per process. Pass `store: "redis"` (`VALKEY_URL` / `REDIS_URL`) or `store: "mongo"` for one shared bucket.
122
+ 4. **To keep HTTP unlimited, pass `rateLimit: false`:**
123
+
124
+ ```typescript
125
+ new TerrenoApp({userModel: User, rateLimit: false});
126
+ ```
127
+
128
+ 5. **Tests:** each `build()` gets a fresh memory store. A suite that logs in more than 20 times or sends more than 600 requests against one app instance should pass `rateLimit: false` in its shared app helper.
129
+
130
+ Apps that already pass `rateLimit: {…}` see no change. `@terreno/api` still does not read `RATE_LIMIT_ENABLED`; an app-owned toggle should now produce `false` to turn the limiter off. See [Rate limiting](../../../docs/how-to/rate-limiting.md).
131
+
132
+ ## Admin no longer takes an RTK `api`
133
+
134
+ `@terreno/admin-frontend` stopped injecting endpoints into your RTK Query API. Every admin request — model CRUD (including ObjectId and API-only models), `/admin/config`, scripts, roles, configuration, documents, comms, jobs, organizations, consent, announcements, and AI observability — now goes through one native `fetch` client that `AdminProvider` binds to your auth. The `api` prop, `useAdminApi`, `AdminApi`, `EndpointBuilder`, and admin `injectEndpoints` are removed. String-`_id` models with `adminBroadcast` keep the windowed syncdb path from 57.
135
+
136
+ Before:
137
+
138
+ ```tsx
139
+ <AdminProvider api={terrenoApi} apiBase="/admin" apiOrigin={baseUrl} getAuthHeaders={getAdminAuthHeaders} routeBase="/admin">
140
+ <AdminShellLayout api={terrenoApi} apiBase="/admin" routeBase="/admin">
141
+ <Stack />
142
+ </AdminShellLayout>
143
+ </AdminProvider>
144
+
145
+ <AdminScreenRouter api={terrenoApi} apiBase="/admin" name={model} routeBase="/admin" />
146
+ <AdminModelTable api={terrenoApi} baseUrl="/admin" modelName="User" />
147
+
148
+ const {useListQuery} = useAdminApi(terrenoApi, "/admin/users", "User");
149
+ const {config} = useAdminConfig(terrenoApi, "/admin");
150
+ ```
151
+
152
+ After:
153
+
154
+ ```tsx
155
+ <AdminProvider apiBase="/admin" apiOrigin={baseUrl} getAuthHeaders={getAdminAuthHeaders} routeBase="/admin">
156
+ <AdminShellLayout apiBase="/admin" routeBase="/admin">
157
+ <Stack />
158
+ </AdminShellLayout>
159
+ </AdminProvider>
160
+
161
+ <AdminScreenRouter apiBase="/admin" name={model} routeBase="/admin" />
162
+ <AdminModelTable baseUrl="/admin" modelName="User" />
163
+
164
+ const {useListQuery} = useAdminModelApi("/admin/users", "User");
165
+ const {config} = useAdminConfig("/admin");
166
+ ```
167
+
168
+ Same-origin cookie SPAs (like `@terreno/admin-spa`) pass `credentials="same-origin"` on `AdminProvider` and omit `apiOrigin` / `getAuthHeaders`. `getAdminAuthHeaders` is your host's `() => ({Authorization: "Bearer …"})` (example-frontend reads the Better Auth session token).
169
+
170
+ Product screens that reuse admin components outside the admin tree (for example `DocumentStorageBrowser` or `ConsentHistory` on a user tab) must render inside an `AdminProvider` with the same binding. Without one, admin hooks fall back to an unauthenticated same-origin `fetch` and warn once in development.
171
+
172
+ Checklist:
173
+
174
+ - [ ] Remove `api={…}` from every `@terreno/admin-frontend` component and from custom admin widget props.
175
+ - [ ] Drop the `api` first argument from `useAdminConfig`, `useAdminScripts`, `useAdminRoles`, `useConfigurationApi`, `useDocumentStorageApi`, `useConsentHistory`, `useOrganizationsApi`, `useCommsDashboardApi`, `useJobsDashboardApi`, and the `useAiObservability*Api` hooks. `useOrganizationsApi` also drops its unused `organizationId` argument.
176
+ - [ ] Query `refetch()` now returns a Promise; wrap fire-and-forget calls as `void refetch()`.
177
+ - [ ] Replace `useAdminApi(api, routePath, name)` with `useAdminModelApi(routePath, name)`. Hook names, `.unwrap()`, and response shapes are unchanged.
178
+ - [ ] Make sure one `AdminProvider` wraps the admin tree with `credentials` (cookie) or `apiOrigin` + `getAuthHeaders` (Bearer).
179
+ - [ ] Wrap any product-side reuse of admin components in `AdminProvider`.
180
+ - [ ] Delete type-erased casts such as `terrenoApi as unknown as AdminScreenProps["api"]` and any admin-only RTK tag types.
181
+ - [ ] Replace custom admin `injectEndpoints` with `useAdminRpc` + `useAdminRpcQuery` / `useAdminRpcMutation`.
182
+
183
+ ## New native modules (rebuild dev clients)
184
+
185
+ Terreno 58 adds every native module the B2B features need in one release, so later features ship as JavaScript and OTA updates against the same binary. A dev client or store build made before 58 does not contain these modules. Apps that call them on an old binary crash or get `null` modules.
186
+
187
+ | Package | Version | Config plugin |
188
+ | --- | --- | --- |
189
+ | `@stripe/stripe-react-native` | `0.81.0` | Yes: `merchantIdentifier`, `enableGooglePay` |
190
+ | `react-native-purchases` | `10.12.2` | None |
191
+ | `react-native-purchases-ui` | `10.12.2` | None (keep the same version as `react-native-purchases`) |
192
+ | `expo-device` | `~58.0.5` | None |
193
+ | `expo-crypto` | `~58.0.2` (unchanged pin) | None |
194
+ | `expo-local-authentication` | `~58.0.2` | Yes: `faceIDPermission` |
195
+ | `expo-system-ui` | `~58.0.6` | Applied automatically |
196
+ | `expo-build-properties` | `~58.0.11` | Yes: Android Kotlin and `compileSdkVersion` pins |
197
+
198
+ `react-native-otp-verify` is not in the baseline. It does not support the New Architecture, the only architecture React Native 0.88 runs. For one-time codes, use `TextInput` with `textContentType="oneTimeCode"` (iOS) and `autoComplete="sms-otp"` (Android), which need no native module.
199
+
200
+ Checklist:
201
+
202
+ 1. Add the packages to the app with `bunx expo install @stripe/stripe-react-native react-native-purchases@10.12.2 react-native-purchases-ui@10.12.2 expo-device expo-crypto expo-local-authentication expo-system-ui expo-build-properties`. In the Terreno monorepo they are root catalog entries; reference them with `"catalog:"`.
203
+ 2. Add the config plugins to `app.json` (or `app.config.ts`). Replace the merchant id with your own Apple Pay merchant id; register it in the Apple Developer portal before the iOS build.
204
+
205
+ ```json
206
+ [
207
+ "expo-build-properties",
208
+ {"android": {"compileSdkVersion": 37, "kotlinVersion": "2.2.21"}}
209
+ ],
210
+ [
211
+ "@stripe/stripe-react-native",
212
+ {"enableGooglePay": true, "merchantIdentifier": "merchant.com.yourcompany.app"}
213
+ ],
214
+ [
215
+ "expo-local-authentication",
216
+ {"faceIDPermission": "Allow $(PRODUCT_NAME) to use Face ID to confirm it is you."}
217
+ ]
218
+ ```
219
+
220
+ React Native 0.88 already compiles against Android SDK 37. Stripe needs at least SDK 36 and Kotlin 2.x; the pins keep both explicit. Kotlin `2.2.21` matches the Stripe Android build default and Expo's KSP table.
221
+ 3. Rebuild every dev client and store binary (`eas build --profile development` for iOS and Android). Expo fingerprints change, so OTA updates from 57 binaries do not apply to 58 binaries.
222
+ 4. Open the demo's hidden **Native Modules** page (`/demo/Native%20Modules`) on the new dev client. Each native module must show **Linked**. Web shows **Native only**.
223
+
224
+ What still works in Expo Go:
225
+
226
+ | Flow | Expo Go |
227
+ | --- | --- |
228
+ | `expo-device`, `expo-crypto`, `expo-system-ui` | Works (bundled in Expo Go) |
229
+ | `expo-local-authentication` | Works, except Face ID on iOS (Expo Go has no Face ID usage string); use a dev build |
230
+ | `react-native-purchases` / `-ui` | Runs in RevenueCat Preview API mode: mock offerings and paywalls, no real purchases |
231
+ | `@stripe/stripe-react-native` | Card flows work (Expo Go 58 bundles `0.81.0`). Apple Pay and Google Pay need a dev build, because the merchant id is set by the config plugin |
232
+ | Web | Unchanged. Native-only calls need a `Platform.OS` guard |
233
+
234
+ ## Legacy RTK realtime is removed
235
+
236
+ Terreno 58 deletes the RTK cache-patching realtime path. Collection live updates go through `@terreno/syncdb`: `sync` on `modelRouter` plus `sync:delta` events. `RealtimeApp` stays: it still hosts Socket.io, socket auth, change streams, and `sync:delta`. `SyncApp` and `admin.realtime` (`admin:model.changed` events) are unchanged. The rest of `@terreno/rtk`, including `useSocketConnection` and `useRealtimeDebug`, is removed too; see [`@terreno/rtk` and Redux are removed](#terrenortk-and-redux-are-removed).
237
+
238
+ Removed from `@terreno/api`:
239
+
240
+ - `modelRouter` option `realtime`. Passing it is now a TypeScript error.
241
+ - The `sync` websocket event. The change stream emits only `sync:delta` for models registered with `sync`.
242
+ - The `subscribe:model`, `subscribe:document`, and `subscribe:query` socket events (and their `unsubscribe:*` pairs, plus `query:subscribed`).
243
+ - Types `RealtimeConfig`, `RealtimeEvent`, `DocumentSubscription`, and `QuerySubscription`.
244
+ - Registry helpers `registerRealtime`, `getRealtimeRegistry`, `findRegistryEntryByCollection`, `findRegistryEntryByRoutePath`, `updateRealtimeRegistryOptions`, `clearRealtimeRegistry`, and type `RealtimeRegistryEntry`.
245
+ - Query store helpers `addQuerySubscription`, `removeQuerySubscription`, `removeAllSocketQueries`, `getQuerySubscriptionsForCollection`, `computeQueryId`, and `clearQueryStore`.
246
+ - Constants `MAX_MODEL_SUBSCRIPTIONS`, `MAX_DOCUMENT_SUBSCRIPTIONS`, and `MAX_QUERY_SUBSCRIPTIONS`.
247
+
248
+ Removed from `@terreno/rtk`:
249
+
250
+ - `realtimeList`, `realtimeDocument`, `setRealtimeSocket`, `getRealtimeSocket`, `useSyncConnection`, and type `RealtimeEvent`.
251
+
252
+ To upgrade:
253
+
254
+ 1. Backend: delete `realtime: {...}` from every `modelRouter` config. For each collection that needs live updates, add `syncPlugin` + `isDeletedPlugin` to the schema and a `sync` config to the router (three-argument form), then register `SyncApp` next to `RealtimeApp`.
255
+ 2. Frontend: delete `setRealtimeSocket(socket)`, `useSyncConnection(...)`, and every `onCacheEntryAdded: realtimeList(...)` / `realtimeDocument(...)` override. Read and write synced collections with `useQuery` / `useMutate` from `@terreno/syncdb/react`.
256
+ 3. Tests: replace `clearRealtimeRegistry()` with `clearSyncRegistry()`, which clears the whole collection catalog.
257
+
258
+ See [How to upgrade from @terreno/rtk to @terreno/syncdb](../../../docs/how-to/migrate-rtk-to-syncdb.md).
259
+
260
+ ## MCP service tokens are a synced collection
261
+
262
+ `McpServiceToken` now has a String `_id` (UUID for new rows), `isDeletedPlugin`, and `syncPlugin`. With `mcpServiceTokens` enabled, `/mcp/service-tokens` is a `modelRouter` synced read-only to its owner (collection `mcp/service-tokens`, owner scope on `userId`). Sync snapshots and deltas never include `tokenHash`. `McpServiceToken.verify` and `/mcp` authentication are unchanged.
263
+
264
+ | 57.x | 58.0.0 |
265
+ |------|--------|
266
+ | `POST /mcp/service-tokens` | `POST /mcp/service-tokens/create` (same body; same one-time `token` response) |
267
+ | `DELETE /mcp/service-tokens/:id` | `POST /mcp/service-tokens/:id/revoke` (response is the token row without `tokenHash`) |
268
+ | `GET /mcp/service-tokens` rows `{id, name, tokenPrefix, …}` with `null` dates | Standard `modelRouter` list rows (`_id`, `id`, unset dates omitted); or `useQuery("mcp/service-tokens")` |
269
+ | `revokeForUser(user, ObjectId)` | `revokeForUser(user, string)` |
270
+
271
+ `POST`, `PATCH`, and `DELETE` on the collection return `405`, and sync client writes are nacked `unauthorized`. Admin delete still revokes: a soft delete is stored as `revokedAt`, not a tombstone. `McpServiceToken.deleteMany` now throws once the collection is synced (use `McpServiceToken.collection.deleteMany` in tests).
272
+
273
+ **Run the data migration before deploying 58.** Existing rows keep their ids as hex strings. Add this file to your `migrations/` directory, dry-run it, then apply it:
274
+
275
+ ```typescript
276
+ // migrations/20261022120000-mcp-service-token-string-ids.ts
277
+ import {mcpServiceTokenStringIdsMigration} from "@terreno/api";
278
+
279
+ export const id = "20261022120000-mcp-service-token-string-ids";
280
+ export const up = mcpServiceTokenStringIdsMigration.up;
281
+ ```
282
+
283
+ The migration is idempotent and has no `down` (new rows use UUIDs). See [Connect an MCP client with a service token](../../../docs/how-to/connect-mcp-service-token.md).
284
+
285
+ ## GPT histories are a synced collection
286
+
287
+ `GptHistory` now has a String `_id` (UUID for new rows), `isDeletedPlugin`, and `syncPlugin`. `/gpt/histories` is registered with `sync: {scope: {type: "owner", field: "userId"}}`, so `SyncApp` serves each user's histories as the collection `gpt/histories`. REST CRUD, `/gpt/prompt`, `/gpt/histories/:id/stream`, the `turn` and `pendingAsks` actions, ratings, and `/gpt/actions` are unchanged except that history ids are Strings. `/gpt/actions` no longer answers 400 `historyId is not valid` for a non-ObjectId id; an unknown id is 404. `AIDataset.historyId` is a String.
288
+
289
+ | 57.x | 58.0.0 |
290
+ |------|--------|
291
+ | `useGetGptHistoriesQuery` + `updateQueryData` / `invalidateTags` | `useQuery("gpt/histories")` and `useMutate("gpt/histories")` from `@terreno/syncdb/react` |
292
+ | `GptHistory.deleteMany(...)` / `updateMany(...)` in app code or tests | Write one history at a time by `_id`; tests use `GptHistory.collection.deleteMany({})` |
293
+ | `history._id` is an `ObjectId` | `history._id` is a `string`; drop `new ObjectId(historyId)` casts and `ObjectId.isValid` checks |
294
+
295
+ **Run the data migration before deploying 58.** Existing histories keep their ids as hex strings, and `AIDataset.historyId` refs are rewritten to match. Add this file to your `migrations/` directory, dry-run it, then apply it:
296
+
297
+ ```typescript
298
+ // migrations/20261022130000-gpt-history-string-ids.ts
299
+ import {gptHistoryStringIdsMigration} from "@terreno/ai";
300
+
301
+ export const id = "20261022130000-gpt-history-string-ids";
302
+ export const up = gptHistoryStringIdsMigration.up;
303
+ ```
304
+
305
+ The migration is idempotent and has no `down` (new rows use UUIDs). See [@terreno/ai reference](../../../docs/reference/ai.md#gpthistory-string-ids).
306
+
307
+ ## `@terreno/rtk` and Redux are removed
308
+
309
+ `@terreno/rtk` is not published for 58, and no Terreno package uses Redux. `@terreno/syncdb` takes over every job it did. The [upgrade guide](../../../docs/how-to/migrate-rtk-to-syncdb.md) walks through each step and maps **every** removed export to its replacement; this section is the summary.
310
+
311
+ | `@terreno/rtk` 57.x | 58.0.0 |
312
+ |---------------------|--------|
313
+ | `emptySplitApi`, `getBaseQuery`, `@rtk-query/codegen-openapi` → `store/openApiSdk.ts` | `createApiClient` (`@terreno/syncdb/api`) + `ApiClientProvider`; `terreno-syncdb-codegen --api-out` → `store/apiSdk.ts` (same hook names) |
314
+ | RTK Query hooks for collection CRUD | `useQuery` / `useMutate` (`@terreno/syncdb/react`) |
315
+ | `api.injectEndpoints(...)` | Document the route in OpenAPI and regenerate, or `useApiQuery` / `useApiMutation` |
316
+ | `createBetterAuthClient` | `createBetterAuthClient` (`@terreno/syncdb/auth`) |
317
+ | `generateBetterAuthSlice` + `selectBetterAuth*` selectors | `SessionProvider` + `useSession()` (`@terreno/syncdb/auth`) |
318
+ | `generateAuthSlice` (JWT) + `useSelectCurrentUserId` | `jwtAuthAdapter` + `useJwtAuth` + `useSession()` (`@terreno/syncdb/auth`) |
319
+ | `getTokenExpirationTimes`, `shouldShowStillThereModal` | Same names (`@terreno/syncdb/auth`) |
320
+ | `baseUrl`, `baseWebsocketsUrl`, `resolveBaseUrls` | Same names (`@terreno/syncdb/config`), plus `appHeaders()` |
321
+ | `useSocketConnection` | The syncdb socket: `useSocketEvent` (`@terreno/syncdb/react`) / `syncDb.onSocketEvent` |
322
+ | `useFeatureFlags(api, …)`, `useTerrenoFeatureFlags(api, …)` | `useFeatureFlags({client, socket, …})`, `useTerrenoFeatureFlags({client, socket, …})` (`@terreno/syncdb/flags`) |
323
+ | `useUpgradeCheck`, `hasPermission`, `canOpenAdminPage`, `installTerrenoDevConsoleLogger`, `useMCPTools` | `useUpgradeCheck`, `hasPermission`, `canOpenAdminPage`, `installDevConsoleLogger`, `useMCPTools({authProvider})` (`@terreno/syncdb/app`) |
324
+ | `@terreno/rtk/buildNumber` | `@terreno/syncdb/buildNumber` |
325
+ | Offline middleware, `useOfflineStatus`, `useServerStatus` | The syncdb outbox; `useSyncStatus()` |
326
+ | `useCan`, `useSelectPermissions`, `createSessionApi`, `useTerrenoChat`, `generateTags`, `populateId`, `registerTerrenoDevStore` | Removed (no replacement needed; see the guide) |
327
+
328
+ Steps:
329
+
330
+ 1. Remove the packages: `bun remove @terreno/rtk @reduxjs/toolkit react-redux redux-persist @rtk-query/codegen-openapi`. Add `@terreno/syncdb` and `bunx expo install expo-sqlite expo-secure-store @react-native-async-storage/async-storage`; rebuild native clients.
331
+ 2. Create one `authProvider` (`betterAuthAdapter(bridgeBetterAuthReactClient(betterAuthClient))`, or `jwtAuthAdapter`) and pass it to both `createSyncDb` and `createApiClient`.
332
+ 3. Replace `<Provider store>` / `<PersistGate>` with `<SessionProvider>`, `<ApiClientProvider>`, and `<SyncDbProvider>`. Replace `useSelector` session reads with `useSession()`; move app-state slices to React context or `useStoredState`.
333
+ 4. Point `bun run sdk` at `terreno-syncdb-codegen --api-out ./store/apiSdk.ts`, regenerate, and change imports from `@/store/openApiSdk` / `@/store/sdk` to `@/store/apiSdk` (`useGetMeQuery` → `useGetAuthMeQuery`).
334
+ 5. Remove `useSocketConnection`; pass `socket: syncDb` to the flag hooks and use `useSocketEvent` for other server pushes. The app opens one socket.
335
+ 6. Verify: `rg -l "@terreno/rtk|@reduxjs/toolkit|react-redux|redux-persist|rtk-query" --glob '!**/node_modules/**' .` returns nothing; compile, lint, and test pass.
336
+
337
+ The feature-flags `GET /evaluate` deprecation warning now points at `@terreno/syncdb/flags`.
338
+
339
+ ### Consent and announcement navigators take a `client`
340
+
341
+ `@terreno/ui` no longer calls into an RTK Query API. `ConsentNavigator`, `AnnouncementNavigator`, and their hooks take a request `client` (`TerrenoRequestClient`; a syncdb `ApiClient` satisfies it).
342
+
343
+ | 57.x | 58.0.0 |
344
+ |------|--------|
345
+ | `<ConsentNavigator api={terrenoApi}>` | `<ConsentNavigator client={apiClient}>` |
346
+ | `<AnnouncementNavigator api={terrenoApi} …>` | `<AnnouncementNavigator client={apiClient} …>` |
347
+ | `useConsentForms(api, baseUrl)`, `useSubmitConsent(api, baseUrl)` | `useConsentForms(client, baseUrl)`, `useSubmitConsent(client, baseUrl)` |
348
+ | `useAnnouncements(api, …)`, `useAcknowledgeAnnouncement(api, …)` | `useAnnouncements(client, …)`, `useAcknowledgeAnnouncement(client, …)` |
349
+
350
+ See [@terreno/ui reference](../../../docs/reference/ui.md).
351
+
352
+ ### Scaffolds, MCP tools, and the CLI
353
+
354
+ `create-terreno-app` and the `terreno_bootstrap` prompt now scaffold a frontend with no Redux and no `@terreno/rtk`: `lib/betterAuth.ts` (`createBetterAuthClient` from `@terreno/syncdb/auth`), `store/syncdb.ts` (shared `authProvider` + `syncDb`), `store/apiClient.ts` (`createApiClient` from `@terreno/syncdb/api`), and `store/apiSdk.ts` (generated hooks). `app/_layout.tsx` mounts `ApiClientProvider` and `SessionProvider`.
355
+
356
+ | 57.x | 58.0.0 |
357
+ |------|--------|
358
+ | `bun run sdk` → `scripts/generate-sdk.ts` + `openapi-config.ts` (`@rtk-query/codegen-openapi`) → `store/openApiSdk.ts` | `bun run sdk` → `api-sdk` script: `terreno-syncdb-codegen --schema <openapi.json> --api-out ./store/apiSdk.ts` |
359
+ | `store/index.ts`, `store/appState.ts`, `store/errors.ts`, `store/sdk.ts`, `store/betterAuthApi.ts`, `tsconfig.codegen.json` | Deleted. Session: `useSession()`; request errors: `createApiClient({onError})` |
360
+ | `useGetMeQuery` / `usePatchMeMutation` from `@/store/sdk` | `useGetAuthMeQuery` / `usePatchAuthMeMutation` from `@/store/apiSdk` |
361
+ | `getAdminAuthHeaders` from `@/store/betterAuthApi` | `getAdminAuthHeaders` from `@/store/apiClient` (reads `authProvider.getToken()`) |
362
+ | MCP `get_rtk_state` (terreno-mcp-local) | `get_syncdb_state`, `syncdb_snapshot`, `syncdb_action` |
363
+ | `terreno state --slice rtk` | `terreno state` (reads `get_syncdb_state`; `--collection`, `--id`, `--limit`) |
364
+ | `terreno generate sdk --config openapi-config.ts` | `terreno generate sdk --schema <openapi.json> --out store/apiSdk.ts` |
365
+ | MCP resource `terreno://docs/rtk` | `terreno://docs/syncdb` |
366
+ | `terreno_generate_screen` list/detail/form used `@/store/openApiSdk` hooks | Uses `useQuery` / `useEntity` / `useMutate` from `@terreno/syncdb/react` |
367
+
368
+ ## Better Auth 1.7 and native sign-in
369
+
370
+ Terreno 58 pins `better-auth` and `@better-auth/expo` to `1.7.4` (the root catalog). `@better-auth/passkey` needs 1.7, and the 1.6 and 1.7 Expo plugins differ on native.
371
+
372
+ ### Pin Better Auth 1.7.4
373
+
374
+ **What changed**
375
+
376
+ `@terreno/syncdb` and `@terreno/api` type-check against Better Auth 1.7. Mixing 1.6 and 1.7 between `better-auth`, `@better-auth/expo`, and `@better-auth/passkey` fails to compile (client plugin types disagree) and breaks passkey sign-in at runtime. Better Auth 1.7 serves provider callbacks at `/callback/:id`; check that any redirect URI you registered with a provider still matches.
377
+
378
+ **Migration**
379
+
380
+ Set all three to the same version in the app (`"catalog:"` in the Terreno monorepo):
381
+
382
+ ```json
383
+ {
384
+ "dependencies": {
385
+ "@better-auth/expo": "1.7.4",
386
+ "@better-auth/passkey": "1.7.4",
387
+ "better-auth": "1.7.4"
388
+ }
389
+ }
390
+ ```
391
+
392
+ ### Native storage needs `getItemAsync` and `setItemAsync`
393
+
394
+ **What changed**
395
+
396
+ The 1.7 Expo client plugin reads and writes its session cookie through `storage.getItemAsync` / `storage.setItemAsync`. A storage object with only `getItem` / `setItem` makes every auth request on iOS and Android throw `undefined is not a function`. Web does not exercise this path, so web e2e passes while a device build is broken.
397
+
398
+ `createBetterAuthClient` from `@terreno/syncdb/auth` supplies both methods through its `StorageAdapter`. Apps that build their own Better Auth client with `expoClient({storage})` must provide them too (`getItemAsync?: (key) => Promise<string | null>`, `setItemAsync?: (key, value) => Promise<void>`).
399
+
400
+ ### Native sign-out needs `nativeApps: true` on the server
401
+
402
+ **What changed**
403
+
404
+ Native requests carry `expo-origin` instead of `Origin`. Without the server-side Expo plugin, `POST /auth/sign-out` answers `403 Missing or null Origin` on iOS and Android, so the user can never sign out.
405
+
406
+ **Migration**
407
+
408
+ Set `nativeApps: true` in the Better Auth server config (`create-terreno-app` and `example-backend` already do):
409
+
410
+ ```typescript
411
+ const config: BetterAuthConfig = {enabled: true, nativeApps: true, /* ... */};
412
+ ```
413
+
414
+ ### Logout waits for the server and keeps the session on failure
415
+
416
+ `useSession().logout()` signs out of the server first, with the session held. While it runs, `useSession().isSigningOut` is `true` and `isLoading` is unchanged, so a navigator gated on `isLoading` keeps its screen. A sign-out that fails or times out (`signOutTimeoutMs`, default 10 seconds) first checks the session. If it is still live, the session and local data stay and `error` is `SIGN_OUT_FAILED_MESSAGE`; if it is gone, local state is cleared. Do not treat `logout()` as instant, and show `isSigningOut` on the sign-out button.
417
+
418
+ ### Keep `SyncDbProvider` mounted across sign-out
419
+
420
+ Mounting `<SyncDbProvider>` only while `userId` is set unmounts every `useQuery` / `useMutate` consumer in the middle of logout, and screens that are still on screen throw. Mount it unconditionally and let `SessionProvider` (`syncDbs={[syncDb]}`) start and stop the client:
421
+
422
+ ```tsx
423
+ // Before
424
+ {userId ? <SyncDbProvider client={syncDb}>{stack}</SyncDbProvider> : stack}
425
+
426
+ // After
427
+ <SyncDbProvider client={syncDb}>{stack}</SyncDbProvider>
428
+ ```
429
+
430
+ Also keep the navigator on screen while a refresh or sign-out runs with a user present: gate the loading screen on `isLoading && !userId`, not on `isLoading` alone.
431
+
432
+ ### Social login, passkeys, two-factor, and account security live in `@terreno/syncdb/auth`
433
+
434
+ 57.x apps that adopted the screens and hooks from `@terreno/rtk` change the import path. Names are unchanged.
435
+
436
+ | 57.x (`@terreno/rtk`) | 58.0.0 (`@terreno/syncdb/auth`) |
437
+ |-----------------------|---------------------------------|
438
+ | `TerrenoLoginScreen`, `useLoginController` | Same |
439
+ | `TerrenoAccountSecurityScreen`, `useAccountSecurity` | Same |
440
+ | `useAuthMethods(authClient)` | Same |
441
+ | `createRememberedAccountsStore`, `signInSocialWithHandoff`, `completeBetterAuthOAuthHandoff` | Same |
442
+ | `createBetterAuthClient({features})` | Same; `features: {multiSession, passkey, twoFactor}` |
443
+ | `selectBetterAuthIsSigningOut` | `useSession().isSigningOut` |
444
+
445
+ `@terreno/syncdb` now has `@better-auth/passkey` and `@terreno/ui` as optional peers (the auth screens render `@terreno/ui`). Add `@terreno/ui` to apps that import `TerrenoLoginScreen` or `TerrenoAccountSecurityScreen`.
446
+
447
+ ### Provisional consent hooks take a `client`
448
+
449
+ `useProvisionalConsent` and `ProvisionalConsentSigner` (`@terreno/ui`) take `client` (a `TerrenoRequestClient`) like the other consent hooks, not `api`.
450
+
451
+ ### `PATCH /auth/me` documents `currentPassword`
452
+
453
+ The OpenAPI body of `PATCH /auth/me` now lists `currentPassword`. Regenerate `store/apiSdk.ts` (`bun run sdk`) so `usePatchAuthMeMutation` types it. The server still refuses a `password` field on that route; use the password-change flow.
454
+
455
+ ### Admin lists: `useAdminRpcQuery` `keepPreviousData`
456
+
457
+ `useAdminRpcQuery` clears its data when the `url` changes, so a body is never shown under the wrong URL. Custom admin screens that page through a list pass `keepPreviousData: true` to keep the previous page visible while the next one loads; without it the list goes blank on every page.
458
+
459
+ ## Example app changes to copy
460
+
461
+ `example-frontend` and `example-backend` are the reference app. Apply these to your own app; each maps to a section above.
462
+
463
+ **Native build config** (needed for EAS and local dev client builds):
464
+
465
+ | File | Change | Why |
466
+ |------|--------|-----|
467
+ | `app.json` | `"disableSPM": true` on the `@stripe/stripe-react-native` plugin | iOS `pod install` fails when Expo's precompiled modules turn Stripe's SPM dynamic framework into a static library. `useFrameworks: "dynamic"` does not fix it |
468
+ | `eas.json` | On the Android `development` build profile: `"resourceClass": "large"` and `"gradleCommand": ":app:assembleDebug -PreactNativeArchitectures=arm64-v8a"` | The default Android worker kills the Gradle daemon (container out of memory); building one ABI also halves the native compile |
469
+ | `package.json` | `"eas-build-post-install"` compiles workspace dependencies in dependency order, not `bun run compile` | A clean EAS checkout has no `dist/`. `--filter '*'` does not order dev and peer dependencies, so `@terreno/test` compiles before `@terreno/ui`. Outside the Terreno monorepo, publish the packages and this hook is not needed |
470
+ | `package.json` | `@terreno/ui` is a dependency whenever you import `@terreno/syncdb/auth` screens | `syncdb` compiles against it |
471
+
472
+ **Auth and session wiring:**
473
+
474
+ | File | Change |
475
+ |------|--------|
476
+ | `lib/betterAuth.ts` | `createBetterAuthClient({features: {multiSession: true, passkey: true, twoFactor: true}})`; `createRememberedAccountsStore`; `getLoginCallbackURL` / `getSecurityCallbackURL` helpers; `brandedOAuthProviders()` driven by the server's `/terreno/methods` instead of `EXPO_PUBLIC_MOCK_OIDC_SIGN_IN` |
477
+ | `app/_layout.tsx` | `SyncDbProvider` always mounted; loading screen only while `isAuthLoading && !userId`; a signed-in user may stay on `/login` (the login screen records the method and navigates itself); only `/signup` redirects signed-in users |
478
+ | `app/login.tsx` | `TerrenoLoginScreen` with `rememberIdentity`, two-factor inline challenge, passkey sign-in, "Continue as" cards |
479
+ | `app/settings/security.tsx` (new) | `TerrenoAccountSecurityScreen`: password, two-factor, passkeys, linked providers, device sessions |
480
+ | `app/(tabs)/profile.tsx` | Changing the email asks for the current password (`currentPassword` on `PATCH /auth/me`); there is no "New Password" field, because `PATCH /auth/me` refuses `password`; the sign-out button shows `isSigningOut` |
481
+ | `app/consent-sign/[userId].tsx` (new) | Provisional consent signing on a member's behalf, via `useProvisionalConsent({client, userId})` |
482
+ | `utils/userSecurityAccess.ts` and the admin user and org member security screens | Admin recovery and per-member security views |
483
+ | `store/apiSdk.ts`, `store/syncDbSdk.ts` | Regenerate: `bun run sdk && bun run sync-sdk` |
484
+
485
+ **Backend (`example-backend`):** `betterAuthConfig.ts` turns on `nativeApps`, `multiSession`, `twoFactor`, and passkeys (passkeys stay off in production while `FRONTEND_URL` is a loopback URL; a browser only accepts a passkey on the `rpID` host or a subdomain of it). It also registers a mock OIDC provider only when `MOCK_OIDC_ISSUER` is set, never in production.
486
+
487
+ **Phone layouts.** The web layouts these screens had do not fit a phone; the example fixes show what to check in your own screens:
488
+
489
+ | Screen | Problem | Fix |
490
+ |--------|---------|-----|
491
+ | `app/(tabs)/ai.tsx` | The fixed chat-history sidebar left no room for the conversation on a phone | Show the sidebar at desktop width only (`isSupportedDesktopWidth`). Below it, add Chats and New chat buttons, a history sheet, and safe-area padding |
492
+ | `components/SyncTodosScreen.tsx` | The header's Browse button squeezed the title into a one-character column | Stack the header: `direction="column" lgDirection="row"`. Use `lg`, not `md`: native breakpoints are sm 320 / md 375 / lg 600 / xl 1024, so every phone is at least `md` |
493
+
494
+ **Verify a device build, not only web.** The native-only breakages in this section (async storage, `nativeApps`, `SyncDbProvider` unmount) pass web e2e. Run the dev client on iOS and Android, sign in, sign out, and sign back in as a second user before shipping.
495
+
496
+ ### Verification
497
+
498
+ ```bash
499
+ rg -l "@terreno/rtk|@reduxjs/toolkit|react-redux|redux-persist|rtk-query" --glob '!**/node_modules/**' . # nothing
500
+ bun pm ls 2>/dev/null | rg "better-auth|@better-auth" # all 1.7.4
501
+ bun run compile && bun run lint && bun test
502
+ ```
12
503
 
13
- Until 58: omit `rateLimit` to leave the limiter off. See [Rate limiting](../../../docs/how-to/rate-limiting.md).
504
+ On a dev client: sign in, open Profile, tap Sign out (it shows progress, then lands on login with no error screen), and confirm `POST /auth/sign-out` returns 200.
@@ -53,9 +53,9 @@ Canonical copy, language rules, and the Django/Rails comparison:
53
53
 
54
54
  | Package | Description |
55
55
  |---------|-------------|
56
- | [@terreno/rtk](reference/legacy/rtk.md) (deprecated for data sync) | OpenAPI SDK, Better Auth Redux, and feature flags for Terreno frontends |
56
+ | [@terreno/rtk](reference/legacy/rtk.md) (removed in 58) | Replaced by `@terreno/syncdb` |
57
57
 
58
- **Migrating from RTK?** See [How to migrate from @terreno/rtk to @terreno/syncdb](how-to/migrate-rtk-to-syncdb.md).
58
+ **Upgrading from RTK?** See [How to upgrade from @terreno/rtk to @terreno/syncdb (Terreno 58)](how-to/migrate-rtk-to-syncdb.md).
59
59
 
60
60
  ## Testing
61
61
 
@@ -64,8 +64,8 @@ violation is an Architecture finding; cite the rule only when the diff breaks it
64
64
  element; Luxon for dates, never `Date` arithmetic or dayjs; React Native Web still
65
65
  works (no web-only or native-only APIs without a platform guard).
66
66
  - **Data layer**: synced collections use `@terreno/syncdb` (`useQuery`/`useMutate`); other
67
- routes use the generated SDK hooks from `@terreno/rtk`; no direct `axios`/`fetch` in
68
- app code; `openApiSdk.ts` is regenerated with `bun run sdk`, never hand-edited, and a
67
+ routes use the generated SDK hooks in `store/apiSdk.ts` (`@terreno/syncdb` request client);
68
+ no direct `axios`/`fetch` in app code; `apiSdk.ts` is regenerated with `bun run sdk`, never hand-edited, and a
69
69
  backend route change ships the regenerated SDK in the same PR.
70
70
  - **Cross-cutting style**: interfaces over types; maps over enums; `const` arrow
71
71
  functions; named exports; RORO (receive an object, return an object); early returns;
@@ -14,14 +14,15 @@ workflow (consent publish, comms message detail, password-on-create).
14
14
 
15
15
  | Host | When to use | Route prefix (`routeBase`) | API prefix (`apiBase`) | Fetch auth |
16
16
  | --- | --- | --- | --- | --- |
17
- | Embedded (`example-frontend/app/admin`) | Admin lives inside the product Expo app | `"/admin"` | `"/admin"` | `getAuthHeaders` → Bearer, plus `apiOrigin` = RTK `baseUrl` so RPC does not hit the Expo origin |
18
- | Standalone (`@terreno/admin-spa`) | Same-origin console served by `AdminSpaServeApp` | `""` (SPA root; mount is `/console`) | `"/admin"` | `credentials="same-origin"`; empty `getAuthHeaders`; omit `apiOrigin` |
17
+ | Embedded (`example-frontend/app/admin`) | Admin lives inside the product Expo app | `"/admin"` | `"/admin"` | `getAuthHeaders` → Bearer, plus `apiOrigin` = `baseUrl` from `@terreno/syncdb/config` so admin requests do not hit the Expo origin |
18
+ | Standalone (`@terreno/admin-spa`) | Same-origin console served by `AdminSpaServeApp` | `""` (SPA root; mount is `/console`) | `"/admin"` | `credentials="same-origin"`; omit `getAuthHeaders` and `apiOrigin` |
19
19
 
20
20
  `apiBase` is the HTTP path for `/config` and CRUD. `routeBase` is the Expo Router
21
21
  prefix the sidebar concatenates onto `/{model.name}` and `/{screen.name}`. Mixing
22
22
  them puts nav on the wrong URL. Embedded hosts must also set `apiOrigin` (the
23
- backend origin, e.g. `http://localhost:4000`) on `AdminProvider`. Native `fetch`
24
- RPC uses that origin to prefix `/admin/config` and `/rbac/roles`. Do not put the
23
+ backend origin, e.g. `http://localhost:4000`) on `AdminProvider`. Every admin
24
+ request (model CRUD and RPC) is a native `fetch` through that provider, which uses
25
+ the origin to prefix `/admin/config`, `/admin/users`, and `/rbac/roles`. Do not put the
25
26
  origin in `apiBase` — that would rewrite in-app navigation.
26
27
 
27
28
  ## Screen kinds
@@ -75,7 +76,7 @@ admin `Stack`); otherwise its default light screen containers cover the canvas.
75
76
  | Field widgets | `admin.fieldOverrides.widget` + `widgets.fields` |
76
77
  | Expo files | `app/admin/_layout.tsx` (shell once) or per-route shell in admin-spa |
77
78
  | Dedicated extra segments (`/comms/:id`) | Explicit Expo files so `[model]/[id]` does not treat them as generic forms |
78
- | Data fetching | REST remains membership/search/sort/pagination. String `_id` models with `adminBroadcast` overlay TinyBase and write through syncdb when the host passes a window client + fetch auth. Framework RPC uses host-bound fetch; ObjectId/API-only CRUD keeps RTK compatibility in Terreno 57. |
79
+ | Data fetching | REST remains membership/search/sort/pagination. String `_id` models with `adminBroadcast` overlay TinyBase and write through syncdb when the host passes a window client + fetch auth. Every admin request goes through the `AdminProvider` host-bound fetch client: ObjectId/API-only CRUD (`useAdminModelApi`) and framework RPC alike. Terreno 58 removed the RTK `api` prop and `useAdminApi`. |
79
80
 
80
81
  ## Related
81
82
 
@@ -18,7 +18,7 @@ registry into `AdminProvider`; `@terreno/admin-frontend` only owns first-party T
18
18
 
19
19
  Opening the admin UI is a separate decision from what an operator may do inside it. The page
20
20
  gate is `admin:access` (`GET /admin/config` 403 without it). Host apps should hide admin
21
- navigation with the same check (`canOpenAdminPage` from `@terreno/rtk`).
21
+ navigation with the same check (`canOpenAdminPage` from `@terreno/syncdb/app`).
22
22
 
23
23
  `AdminApp` then resolves authorization before returning `/admin/config`. The response only contains
24
24
  models and custom screens the caller may read, includes effective write capabilities, and exposes
@@ -110,7 +110,7 @@ Each package has its own CI workflow that runs on pull requests and pushes:
110
110
  |----------|---------|-------|
111
111
  | `api-ci.yml` | @terreno/api | Unit tests, type checking, linting |
112
112
  | `ui-ci.yml` | @terreno/ui | Component tests, type checking, linting |
113
- | `rtk-ci.yml` | @terreno/rtk | Unit tests, type checking, linting |
113
+ | `syncdb-ci.yml` | @terreno/syncdb | Unit tests, type checking, linting |
114
114
  | `mcp-server-ci.yml` | @terreno/mcp | Integration tests, type checking, linting |
115
115
  | `example-backend-ci.yml` | example-backend | Integration tests, type checking |
116
116
  | `example-frontend-ci.yml` | example-frontend | Build verification, type checking |
@@ -184,7 +184,7 @@ Ensures preview environments don't accumulate and waste resources.
184
184
  **Packages published:**
185
185
  - @terreno/api
186
186
  - @terreno/ui
187
- - @terreno/rtk
187
+ - @terreno/syncdb
188
188
  - @terreno/mcp
189
189
 
190
190
  ## Workflow Philosophy
@@ -192,7 +192,9 @@ Ensures preview environments don't accumulate and waste resources.
192
192
  ### Single Source of Truth
193
193
  - **Documentation:** Code is truth, docs must reflect it
194
194
  - **Agent configuration:** `.rulesync/rules/`, `.rulesync/skills/`, and
195
- `.rulesync/hooks.json` are truth; generated files derive from them
195
+ `.rulesync/hooks.json` are truth; generated files derive from them. Root `AGENTS.md`
196
+ and `CLAUDE.md` are generated from `.rulesync/rules/00-root.md` by `bun run rules`
197
+ (rulesync 17), so edit the rule, not the output
196
198
  - **Tests:** Implementation is truth, tests verify it
197
199
 
198
200
  ### Fail Fast