@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
@@ -56,4 +56,4 @@ Today a model can enable both `realtime` (RTK `sync` events + cache patching) an
56
56
  - [x] Using `modelRouter` `realtime` logs a one-time warning that names Terreno 58 and syncdb.
57
57
  - [x] Using `realtimeList` / `realtimeDocument` / `setRealtimeSocket` logs a one-time warning that names Terreno 58 and syncdb.
58
58
  - [x] Task list describes 58 deletion without removing `RealtimeApp`.
59
- - [ ] Terreno 58 removes the deprecated APIs (later).
59
+ - [x] Terreno 58 removes the deprecated APIs.
@@ -0,0 +1,185 @@
1
+ # Implementation Plan: Remove @terreno/rtk and Redux in Terreno 58
2
+
3
+ **Status:** Approved
4
+ **Roadmap:** Area=`syncdb`, Target=`58`, Impact=`Breaking`
5
+ **Branch:** `remove-rtk-58` (one PR into `release-58.0.0`)
6
+ **Owner:** Josh Gachnang
7
+ **Created:** 2026-10-20
8
+ **Target:** Terreno **58.0.0** (breaking)
9
+
10
+ ## Goal
11
+
12
+ Delete the `@terreno/rtk` package and every Redux dependency from the monorepo. After this
13
+ change `@terreno/syncdb` is the only frontend data and session layer:
14
+
15
+ - Synced collections use `useQuery` / `useMutate` (unchanged).
16
+ - Every other HTTP call (`/auth/me`, modelRouter actions, AI, consents, announcements,
17
+ version check) goes through a new syncdb request client with generated, typed hooks.
18
+ - Session state comes from `@terreno/syncdb/auth` (Better Auth, plus a ported JWT adapter).
19
+ - Feature flags and server pushes ride syncdb's existing socket.
20
+ - GptHistory and McpServiceToken become synced collections.
21
+ - All unit and e2e tests exercise these syncdb paths. No test imports RTK or Redux.
22
+
23
+ This fulfils the promise in `rtk/README.md`: rtk is not published in the next major.
24
+
25
+ ## Non-Goals
26
+
27
+ - Backend JWT/Passport auth (`api/src/auth.ts`) stays. Only the frontend RTK slice is replaced.
28
+ - Frozen `website/versioned_docs/**` snapshots are not edited.
29
+ - Syncing any ObjectId model other than GptHistory and McpServiceToken (e.g. User, PushToken,
30
+ AIRequest, AIDataset).
31
+ - Replacing SSE streaming for `/gpt/prompt` and `/gpt/histories/:id/stream`. They stay
32
+ `fetch`-based, authenticated through the new client's auth provider.
33
+ - Converting `api/src/auth.ts` / `authRecovery.ts` `router.*` routes to modelRouter actions.
34
+ Codegen reads them from OpenAPI as-is.
35
+
36
+ ## Decisions
37
+
38
+ | Question | Decision |
39
+ |----------|----------|
40
+ | How far does removal go? | Delete the whole `@terreno/rtk` package. |
41
+ | Which release? | Terreno 58.0.0, on the `release-58.0.0` line. |
42
+ | Does Redux go too? | Yes. Remove `@reduxjs/toolkit`, `react-redux`, `redux-persist`, `@rtk-query/codegen-openapi` from both apps, the generators, and the catalog. App state moves to React context / `useStoredState`. |
43
+ | Where do non-collection calls go? | Inside `@terreno/syncdb`: a request client + `useApiQuery` / `useApiMutation`, generalized from admin-frontend's `adminRequest` / `useAdminRpc`. |
44
+ | GptHistory / McpServiceToken (ObjectId)? | Make both synced collections. |
45
+ | GptHistory data migration | Migration shipped in `@terreno/ai` re-inserts each history with `_id` = its ObjectId hex string, rewrites `AIDataset.historyId`, deletes originals. New rows get UUIDs. |
46
+ | McpServiceToken shape | Synced read-only (sync `responseHandler` strips `tokenHash`; client create/update/delete disabled). Create and revoke stay modelRouter actions called through the request client. Same hex-string `_id` migration. |
47
+ | Home for non-data leftovers | syncdb subpaths: `/auth`, `/config`, `/flags`, `/app`. |
48
+ | Replacement for `useSocketConnection` | An event-subscription API on syncdb's existing socket. Flags refetch on push. One socket per app. |
49
+ | Codegen scope | Typed hooks for **every** non-synced OpenAPI operation (anything without `x-terreno-sync`). Replaces `@rtk-query/codegen-openapi` and the RTK `bun run sdk`. |
50
+ | `@terreno/ui` consent / announcement hooks | Take a structural `client` prop (minimal request-client interface declared in ui). ui gains no dependency on syncdb. |
51
+ | rtk features with no consumers | Delete offline middleware, `sessionApi`, `useServerStatus`, `useCan`, `useTerrenoChat`, `generateTags`, `mongooseSlice`, dev store. **Port** legacy JWT frontend auth into `@terreno/syncdb/auth`. Replace MCP `get_rtk_state` and `terreno state --slice rtk` with the existing syncdb state tools. |
52
+ | PR shape | One PR. Tasks still land expand → migrate → contract on the branch, each leaving the branch green. |
53
+ | 13 frontend-called routes are missing from OpenAPI (`/auth/me` + auth recovery, flags, version check, consents, announcements, `/files/upload`) — how does codegen cover them? | Document them in OpenAPI in their owning packages (`api`, `feature-flags`, `announcements`, `ai`) so codegen covers every call and `/auth/me` gets the app's own User type. |
54
+
55
+ ## Architecture
56
+
57
+ ### Before
58
+
59
+ ```
60
+ example-frontend / admin-spa
61
+ Redux store ── betterAuth slice (@terreno/rtk) ── appState slice ── redux-persist
62
+ └── RTK Query api (store/betterAuthApi.ts + generated openApiSdk.ts)
63
+ @terreno/rtk: useSocketConnection, useFeatureFlags, useUpgradeCheck, baseUrl, ...
64
+ @terreno/syncdb: synced collections only (baseUrl passed in from rtk)
65
+ ```
66
+
67
+ ### After
68
+
69
+ ```
70
+ @terreno/syncdb
71
+ . createSyncDb, collections, sync socket (+ new socket event API)
72
+ /react useQuery, useMutate, ... + useApiQuery, useApiMutation, ApiClientProvider
73
+ /api createApiClient (fetch; auth from AuthProvider; envelope unwrap; tags)
74
+ /auth createBetterAuthClient, betterAuthAdapter, SessionProvider/useSession,
75
+ jwtAuthAdapter + useJwtAuth (ported JWT login/refresh/logout)
76
+ /config baseUrl, baseWebsocketsUrl, resolveBaseUrls, resolveDevApiPort
77
+ /flags useFeatureFlags, useTerrenoFeatureFlags (client + socket push, OpenFeature)
78
+ /app useUpgradeCheck, canOpenAdminPage/hasPermission, installDevConsoleLogger,
79
+ useMCPTools
80
+ /buildNumber coerceBuildNumber, resolveBuildNumber (node-only)
81
+ codegen `terreno-syncdb generate` emits collection hooks (today) AND typed hooks
82
+ for every non-synced operation (new)
83
+ ```
84
+
85
+ The request client and syncdb share one `AuthProvider` (`getToken`, `refresh`, `onAuthChange`),
86
+ so 401 → refresh → retry behaves the same for sync and RPC. The client keeps
87
+ `useAdminRpc`'s call shape (`{data, error, isLoading, isFetching, refetch}`,
88
+ `[trigger, {isLoading}]` with `.unwrap()`), plus what the app needs that admin did not: a
89
+ shared per-client cache keyed by URL+args (dedupes identical queries), lazy queries,
90
+ imperative `client.request`, tag invalidation, `App-Version` / `App-Platform` headers,
91
+ and `credentials` for cookie SPAs.
92
+
93
+ admin-frontend's `adminRequest` / `useAdminRpc` become thin wrappers over the syncdb client so
94
+ there is one implementation.
95
+
96
+ ### Session without Redux
97
+
98
+ `SessionProvider` subscribes to the Better Auth session atom (already bridged by
99
+ `bridgeBetterAuthReactClient`) and exposes `useSession()` → `{userId, user, isLoading,
100
+ isAuthenticated, error, logout}`. `logout` signs out, stops syncdb, and wipes local data.
101
+ JWT apps use `jwtAuthAdapter` (SecureStore / AsyncStorage tokens, mutex refresh, refresh
102
+ when < 2 min to expiry, 401 → refresh → retry) and `useJwtAuth()` (`login`, `signup`,
103
+ `googleLogin`, `resetPassword`, `logout`), feeding the same `SessionProvider`.
104
+
105
+ ### Socket events
106
+
107
+ syncdb's socket transport gains `client.onSocketEvent(name, handler) → unsubscribe` and
108
+ `useSocketEvent(name, handler)`. The RealtimeApp server already hosts that socket; feature
109
+ flags subscribe to their existing push event and refetch `/feature-flags/flagConfiguration`.
110
+
111
+ ## Models
112
+
113
+ | Model | Change |
114
+ |-------|--------|
115
+ | `GptHistory` (`ai/src/models/gptHistory.ts`) | `_id: String` (UUID default), `isDeletedPlugin` + `syncPlugin`. |
116
+ | `AIDataset.historyId` (`ai/src/models/aiDataset.ts`) | Ref type `String`. |
117
+ | `McpServiceToken` (`api/src/models/mcpServiceToken.ts`) | `_id: String`, `isDeletedPlugin` + `syncPlugin`. |
118
+
119
+ Migrations (`api/src/migrations` framework):
120
+
121
+ - `@terreno/ai` migration `gpt-history-string-ids`: for each ObjectId `_id` row, insert a copy with
122
+ `_id = oid.toHexString()`, update `AIDataset.historyId` refs to the hex string, delete the
123
+ original. Idempotent (skips rows whose `_id` is already a string). Runs in batches.
124
+ - `@terreno/api` migration `mcp-service-token-string-ids`: same approach for McpServiceToken.
125
+
126
+ ## APIs
127
+
128
+ | Route | Change |
129
+ |-------|--------|
130
+ | `/gpt/histories` | `modelRouter` gains `sync: {scope: owner}`. Existing REST CRUD stays for headless clients. |
131
+ | `/gpt/prompt`, `/gpt/histories/:id/stream`, `/gpt/histories/:id/turn`, `/gpt/actions` | Accept string history ids. |
132
+ | `/mcp/service-tokens` | Becomes `modelRouter("/mcp/service-tokens", McpServiceToken, {sync, ...})` with client writes disabled; `collectionActions.create` returns the plaintext once; `instanceActions.revoke`. |
133
+ | Feature-flags deprecation message (`feature-flags/src/featureFlagsApp.ts:150`) | Names `@terreno/syncdb/flags`. |
134
+
135
+ ## UI
136
+
137
+ - `ConsentNavigator`, `AnnouncementNavigator` and their hooks take `client: TerrenoRequestClient`
138
+ (structural interface in `ui/src/requestClient.ts`) instead of `api`.
139
+ - No visual change. example-frontend's AI screen, MCP settings, profile, admin layout, auth screens,
140
+ documents, and notifications must behave the same; verified with screenshots in the PR.
141
+
142
+ ## Phases
143
+
144
+ 1. **Expand (syncdb):** request client + hooks, codegen for non-synced ops, `/config`, `/auth`
145
+ (Better Auth + JWT), socket events + `/flags`, `/app`.
146
+ 2. **Backend sync:** GptHistory and McpServiceToken synced, with migrations.
147
+ 3. **Migrate consumers:** ui, example-frontend, admin-spa, admin-frontend, generators, MCP/cli tools,
148
+ tests.
149
+ 4. **Contract:** delete `rtk/`, CI, scripts, configs; docs and rules; full verification.
150
+
151
+ ## Feature Flags & Migrations
152
+
153
+ - No runtime flag; this is a major-version break.
154
+ - Two data migrations (above). Upgrade notes tell operators to run migrations before deploying
155
+ 58 frontends.
156
+
157
+ ## Not Included / Future Work
158
+
159
+ - Syncing User/profile (`/auth/me`) or other ObjectId models.
160
+ - Moving SSE chat streaming onto sync deltas.
161
+
162
+ ## Files to Create / Modify
163
+
164
+ See [`docs/tasks/remove-rtk.md`](../tasks/remove-rtk.md) for per-task files.
165
+
166
+ ## Task List
167
+
168
+ [`docs/tasks/remove-rtk.md`](../tasks/remove-rtk.md)
169
+
170
+ ## Acceptance Criteria
171
+
172
+ | # | Criterion | Verification |
173
+ |---|-----------|--------------|
174
+ | AC1 | `rtk/` does not exist and no workspace references it. | `test ! -d rtk`; `rg -l '@terreno/rtk' -g '!website/versioned_docs/**' -g '!CHANGELOG.md' -g '!changelog/**' -g '!docs/implementationPlans/**' -g '!docs/tasks/**' -g '!**/upgrades/**'` → no hits outside upgrade/migration prose. |
175
+ | AC2 | No Redux in the repo's packages. | `rg -l '@reduxjs/toolkit\|react-redux\|redux-persist\|@rtk-query/codegen-openapi' -g 'package.json' -g '*.ts' -g '*.tsx' -g 'bun.lock'` → no hits. |
176
+ | AC3 | Everything builds and every suite passes with no test importing or mocking RTK. | `bun install && bun run compile`; `bun run test:agent` green; `rg -l "store/sdk\|openApiSdk\|betterAuthApi\|injectEndpoints" -g '*.test.*' -g '*.isolated.*' -g '*.spec.ts'` → no hits. |
177
+ | AC4 | E2E and Maestro pass; RTK console-allowlist entries are gone and no new allowlist entries were added. | `bun run frontend:e2e` (all shards), `bun run admin-spa:test` + its e2e, `bun run maestro:test`; `rg "rejected query\|rejected mutation\|terrenoFlagConfiguration\|SocketConnection" example-frontend/e2e` → no hits. |
178
+ | AC5 | GptHistory and McpServiceToken migrations rewrite ids and refs and are idempotent. | Bun tests in `ai/` and `api/` against the in-memory replica set: seed ObjectId rows + AIDataset refs → run twice → string ids, refs match, counts equal. |
179
+ | AC6 | Synced McpServiceToken payloads never contain `tokenHash`; plaintext is returned only by the create action. | `api` sync snapshot + delta tests assert absence; action test asserts plaintext once. |
180
+ | AC7 | example-frontend opens exactly one socket.io connection when logged in, and a flag change pushed by the server updates the UI. | Playwright spec counting `socket.io` websocket connections; flags spec toggling a flag via API. |
181
+ | AC8 | Generated SDK covers every non-synced operation and is typed. | syncdb codegen tests against fixture spec; `example-frontend` `bun run sdk` then `bun run compile`. |
182
+ | AC9 | JWT frontend auth works without Better Auth. | Ported bun tests from `authSlice` / `emptyApi` (login, refresh before expiry, 401 refresh-retry, mutex, logout) pass against `jwtAuthAdapter`. |
183
+ | AC10 | Scaffolded apps build without rtk/Redux. | `create-terreno-app` generate tests + compile of a generated app; `mcp-server` tests. |
184
+ | AC11 | Changed screens verified in a running app. | Screenshots/videos under `/opt/cursor/artifacts/` attached to the PR: login, profile, AI chat (list/rename/delete history), MCP tokens, consents, announcements, admin layout, admin-spa login. |
185
+ | AC12 | Docs and rules describe the new layout; nothing tells users to install `@terreno/rtk`. | `bun run skills:sync` and rulesync regenerate cleanly; `website` docs-audit passes; 58.0.0 upgrade notes cover every removed export with its replacement. |
@@ -27,7 +27,7 @@ Technical reference for Terreno packages and APIs. Information-oriented, precise
27
27
 
28
28
  ## Legacy
29
29
 
30
- `@terreno/rtk` is deprecated for **data synchronization**. New apps should use [`@terreno/syncdb`](syncdb.md). Existing RTK consumers: follow [Migrate from @terreno/rtk to @terreno/syncdb](../how-to/migrate-rtk-to-syncdb.md). Archived RTK reference: [legacy/rtk.md](legacy/rtk.md). `modelRouter` `realtime` and the RTK cache-patching helpers are removed in Terreno 58.
30
+ `@terreno/rtk` and Redux were removed in Terreno 58; [`@terreno/syncdb`](syncdb.md) replaces them. Upgrading: [How to upgrade from @terreno/rtk to @terreno/syncdb (Terreno 58)](../how-to/migrate-rtk-to-syncdb.md) maps every removed export. Stub: [legacy/rtk.md](legacy/rtk.md).
31
31
 
32
32
  ## Configuration
33
33
 
@@ -10,14 +10,25 @@ and [Build admin screens](../how-to/build-admin-screens.md).
10
10
  Home is `AdminHome` inside `AdminProvider` + `AdminShellLayout`. Generic models use
11
11
  `AdminScreenRouter` on `[model]/index`. See the how-to for `apiBase` vs `routeBase`.
12
12
 
13
- Pass fetch auth on `AdminProvider`:
13
+ `AdminProvider` is required. It binds host auth onto one fetch client, and every
14
+ admin request goes through it: model CRUD, `/admin/config`, scripts, roles,
15
+ configuration, documents, comms, jobs, organizations, consent, announcements,
16
+ version-config, background-tasks, migrations, AI observability, and the object
17
+ picker. Admin components take no `api` prop (the RTK `api` prop was removed in Terreno 58).
14
18
 
15
19
  | Host | Props |
16
20
  | --- | --- |
17
- | Standalone SPA | `credentials="same-origin"` and `getAuthHeaders` that return `{}` (cookie session). Omit `apiOrigin`. |
18
- | Embedded app | `getAuthHeaders` that return `Authorization: Bearer …`, plus `apiOrigin` set to the API origin (`@terreno/rtk` `baseUrl`) |
19
-
20
- RPC that has left RTK uses `bindAdminRequest({credentials, getAuthHeaders, origin})` then `adminRequest`. Relative URLs such as `/admin/config` are prefixed with `origin` when it is set. When `AdminProvider` has `credentials` or `getAuthHeaders`, RPC hooks (`useAdminConfig`, scripts, roles, configuration, documents, comms, consent, version-config, background-tasks, AI explorer, object picker) use that client. Successful comms, scripts, and configuration mutations invalidate mounted fetch queries through the same tag contracts as RTK; those background refetches keep cached data with `isLoading: false` and report `isFetching: true`. Passing only `api` keeps `injectEndpoints`. Do not add axios.
21
+ | Standalone SPA | `credentials="same-origin"` (cookie session). Omit `apiOrigin` and `getAuthHeaders`. |
22
+ | Embedded app | `getAuthHeaders` that return `Authorization: Bearer …`, plus `apiOrigin` set to the API origin (`baseUrl` from `@terreno/syncdb/config`) |
23
+
24
+ `AdminProvider` calls `bindAdminRequest({credentials, getAuthHeaders, origin: apiOrigin})`
25
+ and exposes the result through `useAdminRpc()`. Relative URLs such as `/admin/config` are
26
+ prefixed with `origin` when it is set. Successful mutations invalidate mounted queries
27
+ through tags; those background refetches keep cached data with `isLoading: false` and
28
+ report `isFetching: true`. An admin screen rendered outside `AdminProvider` falls back to
29
+ an unauthenticated same-origin `fetch` and warns once in development. Product screens that
30
+ reuse admin components (for example `DocumentStorageBrowser` or `ConsentHistory`) wrap
31
+ them in `AdminProvider` with the same auth binding. Do not add axios.
21
32
 
22
33
  Optional `syncDb` on `AdminProvider` enables windowed changelists for models whose
23
34
  config includes `adminBroadcast: true` and `syncCollection`. When product screens
@@ -29,11 +40,10 @@ admin-frontend.
29
40
  ``````typescript
30
41
  // app/admin/index.tsx
31
42
  import {AdminHome} from "@terreno/admin-frontend";
32
- import {api} from "@/store/openApiSdk";
33
43
 
34
- export default function AdminScreen() {
35
- return <AdminHome api={api} apiBase="/admin" routeBase="/admin" />;
36
- }
44
+ // Rendered inside the AdminProvider in app/admin/_layout.tsx.
45
+ const AdminScreen: React.FC = () => <AdminHome apiBase="/admin" routeBase="/admin" />;
46
+ export default AdminScreen;
37
47
  ``````
38
48
 
39
49
  ## Components
@@ -50,7 +60,6 @@ callback only navigates.
50
60
  const {selectOrganization} = useOrgContext();
51
61
 
52
62
  <OrgDirectoryScreen
53
- api={api}
54
63
  isOperator={currentUser.roles?.includes("operator") ?? false}
55
64
  onEnterOrganization={(organization) => {
56
65
  selectOrganization(organization);
@@ -65,7 +74,6 @@ navigation. Org-admins do not receive this link:
65
74
 
66
75
  ``````typescript
67
76
  <AdminShellLayout
68
- api={api}
69
77
  apiBase="/admin"
70
78
  isOrganizationOperator={isOperator}
71
79
  organizationDirectoryPath="/orgs"
@@ -80,12 +88,8 @@ navigation. Org-admins do not receive this link:
80
88
  Wrap organization-aware admin routes with `OrgContextProvider` and render
81
89
  `OrgSwitcher` through the shell's `organizationSwitcher` slot. Selecting an
82
90
  organization navigates to `{routeBase}/orgs/:orgId` and adds
83
- `X-Organization-Id` to subsequent `useAdminApi` requests. Query cache keys also
84
- include the organization id, preventing rows cached for one org from appearing
85
- in another. `useAdminApi`'s `useListQuery` and `useReadQuery` carry the organization
86
- in the query argument, so a switch after a request started (such as the switcher
87
- settling on a default organization) fetches the new organization's rows instead of
88
- leaving the screen loading. Derive `initialOrganization` from the current pathname so context
91
+ `X-Organization-Id` to subsequent `useAdminModelApi` requests. Queries re-run when
92
+ the organization changes, so rows loaded for one org do not appear in another. Derive `initialOrganization` from the current pathname so context
89
93
  follows URL changes after mount without overriding a switcher selection while
90
94
  navigation is in progress.
91
95
 
@@ -98,9 +102,8 @@ const routeOrganization = useMemo(
98
102
 
99
103
  <OrgContextProvider initialOrganization={routeOrganization}>
100
104
  <AdminShellLayout
101
- api={api}
102
105
  apiBase="/admin"
103
- organizationSwitcher={<OrgSwitcher api={api} routeBase="/admin" />}
106
+ organizationSwitcher={<OrgSwitcher routeBase="/admin" />}
104
107
  routeBase="/admin"
105
108
  >
106
109
  {children}
@@ -320,10 +323,7 @@ to `[model]`.
320
323
  Entry screen showing all available models as cards.
321
324
 
322
325
  ``````typescript
323
- <AdminModelList
324
- baseUrl="/admin"
325
- api={api}
326
- />
326
+ <AdminModelList baseUrl="/admin" />
327
327
  ``````
328
328
 
329
329
  Fetches config from `{baseUrl}/config` and displays clickable model cards.
@@ -333,11 +333,7 @@ Fetches config from `{baseUrl}/config` and displays clickable model cards.
333
333
  Table view for a specific model with pagination, sorting, and actions.
334
334
 
335
335
  ``````typescript
336
- <AdminModelTable
337
- baseUrl="/admin"
338
- api={api}
339
- modelName="User"
340
- />
336
+ <AdminModelTable baseUrl="/admin" modelName="User" />
341
337
  ``````
342
338
 
343
339
  Features:
@@ -346,13 +342,12 @@ Features:
346
342
  - Declared `filters` map to DataTable column filters (`text` contains, `choice`
347
343
  multi `$in`, `boolean`, `dateRange`, `ref` via `AdminRefField` in `renderFilter`)
348
344
  - Optional choice fields include **Empty**, which matches missing and null values.
349
- Admin list requests serialize nested operators with bracket notation independently
350
- of the host application's RTK base-query configuration.
345
+ Admin list requests serialize nested operators with bracket notation (`qs`).
351
346
  - Click row to edit
352
347
  - "Create New" button
353
348
  - Pagination controls
354
349
  - Reference fields render as clickable links
355
- - Windowed TinyBase path when `AdminProvider` has `syncDb` plus a fetch client (`credentials` or `getAuthHeaders`) and `GET /admin/config` reports `adminBroadcast` + `syncCollection` on a String `_id` model: REST list is membership only, rows overlay TinyBase, and a TinyBase table listener rerenders known rows as `{collection}|admin` deltas arrive. **Refresh** (`testID="admin-table-refresh"`) re-queries REST and calls `hydrateWindow`. **Create** (`testID="admin-create-button"`) is in the table chrome, not the navigator header, because admin stacks use `headerShown: false`. **Save** / **Delete** (`testID="admin-save-button"` / `admin-delete-button`) are in the form chrome for the same reason. Page select-all and bulk actions use the rendered rows, so a row a live tombstone removed leaves the selection. RTK `refetch` error envelopes (`error` / `isError`) toast and skip hydrate; an in-flight Refresh is discarded when page, search, or sort changes. A windowed create or delete never touches the cached REST list, so the form flags the collection through `markAdminWindowMembershipStale` and the changelist refetches membership automatically — whether it stayed mounted behind the form or remounts when the form pops. A create also passes the new id as `awaitId`, because `mutate` only enqueues on the outbox and the first refetch can beat the server; the changelist retries up to three times, 700 ms apart — including after transient list failures — then leaves **Refresh** as the fallback. Passing only `api` keeps the RTK list.
350
+ - Windowed TinyBase path when `AdminProvider` has `syncDb` and `GET /admin/config` reports `adminBroadcast` + `syncCollection` on a String `_id` model: REST list is membership only, rows overlay TinyBase, and a TinyBase table listener rerenders known rows as `{collection}|admin` deltas arrive. **Refresh** (`testID="admin-table-refresh"`) re-queries REST and calls `hydrateWindow`. **Create** (`testID="admin-create-button"`) is in the table chrome, not the navigator header, because admin stacks use `headerShown: false`. **Save** / **Delete** (`testID="admin-save-button"` / `admin-delete-button`) are in the form chrome for the same reason. Page select-all and bulk actions use the rendered rows, so a row a live tombstone removed leaves the selection. Membership `refetch` errors (`error` / `isError`) toast and skip hydrate; an in-flight Refresh is discarded when page, search, or sort changes. A windowed create or delete never touches the cached REST list, so the form flags the collection through `markAdminWindowMembershipStale` and the changelist refetches membership automatically — whether it stayed mounted behind the form or remounts when the form pops. A create also passes the new id as `awaitId`, because `mutate` only enqueues on the outbox and the first refetch can beat the server; the changelist retries up to three times, 700 ms apart — including after transient list failures — then leaves **Refresh** as the fallback. Other models use the REST list from `useAdminModelApi`.
356
351
  - Organization-scoped models report `organizationScoped: true` and
357
352
  `adminBroadcast: false`. They use REST list and mutation paths until the sync
358
353
  window protocol carries selected-organization context. Home widgets delay
@@ -369,7 +364,6 @@ Create or edit form for a model instance.
369
364
  ``````typescript
370
365
  <AdminModelForm
371
366
  baseUrl="/admin"
372
- api={api}
373
367
  modelName="User"
374
368
  id="507f1f77bcf86cd799439011" // Optional for edit mode
375
369
  />
@@ -398,12 +392,12 @@ items follow the same default rules.
398
392
 
399
393
  System fields (`_id`, `__v`, `created`, `updated`, `deleted`) are automatically skipped.
400
394
 
401
- When `AdminProvider` has `syncDb` plus a fetch client and the model config reports
395
+ When `AdminProvider` has `syncDb` and the model config reports
402
396
  `adminBroadcast`, `syncCollection`, and a String `_id`, create/update/delete use the
403
397
  syncdb mutation outbox. Edit update/delete first hydrate the REST-loaded record so a
404
398
  deep-linked form can mutate locally. ObjectId models and hosts without the full
405
- windowed configuration keep the REST/RTK mutation path. Organization-scoped
406
- models always use REST/RTK regardless of String `_id`.
399
+ windowed configuration use REST fetch CRUD (`useAdminModelApi`). Organization-scoped
400
+ models always use REST regardless of String `_id`.
407
401
 
408
402
  Pass the host's `useConflicts()` result as `syncConflicts` on `AdminProvider`.
409
403
  Windowed form mutations set their own pending state before any asynchronous work, so
@@ -415,14 +409,15 @@ resolver. Only the most recently mounted sheet per collection renders, so a form
415
409
  stacked over its changelist shows one sheet instead of two.
416
410
  This adapter keeps `@terreno/syncdb` optional for admin-frontend.
417
411
  Bulk actions remain server operations: windowed models call the host fetch
418
- client at `{routePath}/bulk-patch`, while API-only/ObjectId hosts retain RTK.
412
+ client at `{routePath}/bulk-patch` for every model.
419
413
 
420
414
  ```tsx
421
415
  const syncConflicts = useConflicts();
422
416
 
423
417
  <AdminProvider
424
- api={api}
425
418
  apiBase="/admin"
419
+ apiOrigin={baseUrl}
420
+ getAuthHeaders={getAdminAuthHeaders}
426
421
  syncConflicts={syncConflicts}
427
422
  syncDb={syncDb}
428
423
  >
@@ -523,22 +518,20 @@ Provisional display:
523
518
  Fetches admin configuration from backend.
524
519
 
525
520
  ``````typescript
526
- const {config, isLoading, error} = useAdminConfig(api, baseUrl);
521
+ const {config, isLoading, error} = useAdminConfig("/admin");
527
522
  ``````
528
523
 
529
524
  Returns model metadata from `{baseUrl}/config`.
530
525
 
531
- ### useAdminApi
532
-
533
- > Deprecated in Terreno 57: do not add new admin `injectEndpoints`. Terreno 58
534
- > removes `useAdminApi` and the required `api` prop. During the compatibility
535
- > window, continue passing `api` for ObjectId model CRUD and API-only hosts.
526
+ ### useAdminModelApi
536
527
 
537
- Generates compatibility RTK Query hooks for list/read/create/update/delete plus
538
- `POST {routePath}/bulk-patch`.
539
- Pass the model's `routePath` from config (for example `/admin/users` or `/admin/todos`), not the admin `baseUrl`.
540
-
541
- Admin RPC that is leaving RTK uses native `adminRequest` (`AbortController` timeout, JSON or `FormData`, `credentials` forwarded). Bind host auth with `bindAdminRequest`. Do not add axios.
528
+ Fetch CRUD hooks for one admin model over the `AdminProvider` client: list, read,
529
+ create, update, delete, plus `POST {routePath}/bulk-patch`. Pass the model's
530
+ `routePath` from config (for example `/admin/users` or `/admin/todos`), not the admin
531
+ `baseUrl`. List resolves to the `{data, more, page, limit, total}` envelope; read,
532
+ create, and update resolve to the document. Writes refetch mounted list and read
533
+ queries for the same model, and organization-scoped admin sends `X-Organization-Id`.
534
+ Replaces the Terreno 57 `useAdminApi(api, routePath, modelName)` RTK hook.
542
535
 
543
536
  ``````typescript
544
537
  const {
@@ -548,68 +541,90 @@ const {
548
541
  useUpdateMutation,
549
542
  useDeleteMutation,
550
543
  useBulkPatchMutation,
551
- } = useAdminApi(api, "/admin/users", "User");
544
+ } = useAdminModelApi("/admin/users", "User");
552
545
 
553
- const {data, isLoading} = useListQuery({limit: 20, page: 1, q: "Ada", sort: "-created"});
546
+ const {data, isLoading, refetch} = useListQuery({limit: 20, page: 1, q: "Ada", sort: "-created"});
554
547
  const [create] = useCreateMutation();
555
548
  await create({email: "user@example.com"}).unwrap();
556
549
  const [bulkPatch] = useBulkPatchMutation();
557
550
  await bulkPatch({ids: ["abc"], patch: {name: "Ada"}}).unwrap();
558
551
  ``````
559
552
 
553
+ ### useAdminRpc, useAdminRpcQuery, useAdminRpcMutation
554
+
555
+ `useAdminRpc()` returns the `AdminProvider` fetch client. `useAdminRpcQuery({rpc, url,
556
+ providesTags, headers, skip, pollingInterval, transformResponse})` runs a GET and
557
+ returns `{data, error, isError, isLoading, isFetching, refetch}`;
558
+ `useAdminRpcMutation(rpc, {invalidatesTags})` returns `[trigger, {isLoading}]` where
559
+ `trigger(args).unwrap()` sends the request. Use these for custom admin RPC. The
560
+ client is `adminRequest`, built on `apiFetch` from `@terreno/syncdb/api` (timeout, JSON or
561
+ `FormData`, `credentials` forwarded); `unwrapAdminEnvelope` unwraps `{data}` responses the
562
+ same way the syncdb request client does.
563
+
560
564
  ## Expo Router Setup
561
565
 
562
566
  ``````typescript
563
567
  // app/admin/_layout.tsx
568
+ import {AdminProvider} from "@terreno/admin-frontend";
569
+ import {baseUrl} from "@terreno/syncdb/config";
564
570
  import {Stack} from "expo-router";
571
+ import {getAdminAuthHeaders} from "@/store/syncdb"; // () => ({Authorization: "Bearer …"})
565
572
 
566
573
  export default function AdminLayout() {
567
- return <Stack screenOptions={{headerShown: false}} />;
574
+ return (
575
+ <AdminProvider apiBase="/admin" apiOrigin={baseUrl} getAuthHeaders={getAdminAuthHeaders} routeBase="/admin">
576
+ <Stack screenOptions={{headerShown: false}} />
577
+ </AdminProvider>
578
+ );
568
579
  }
569
580
 
570
581
  // app/admin/index.tsx - Model list
571
582
  import {AdminModelList} from "@terreno/admin-frontend";
572
- export default () => <AdminModelList baseUrl="/admin" api={api} />;
583
+ export default () => <AdminModelList baseUrl="/admin" />;
573
584
 
574
585
  // app/admin/[modelName]/index.tsx - Model table
575
586
  import {AdminModelTable} from "@terreno/admin-frontend";
576
587
  export default () => {
577
588
  const {modelName} = useLocalSearchParams();
578
- return <AdminModelTable baseUrl="/admin" api={api} modelName={modelName} />;
589
+ return <AdminModelTable baseUrl="/admin" modelName={modelName} />;
579
590
  };
580
591
 
581
592
  // app/admin/[modelName]/new.tsx - Create form
582
593
  import {AdminModelForm} from "@terreno/admin-frontend";
583
594
  export default () => {
584
595
  const {modelName} = useLocalSearchParams();
585
- return <AdminModelForm baseUrl="/admin" api={api} modelName={modelName} />;
596
+ return <AdminModelForm baseUrl="/admin" modelName={modelName} />;
586
597
  };
587
598
 
588
599
  // app/admin/[modelName]/[id].tsx - Edit form
589
600
  import {AdminModelForm} from "@terreno/admin-frontend";
590
601
  export default () => {
591
602
  const {modelName, id} = useLocalSearchParams();
592
- return <AdminModelForm baseUrl="/admin" api={api} modelName={modelName} id={id} />;
603
+ return <AdminModelForm baseUrl="/admin" modelName={modelName} id={id} />;
593
604
  };
594
605
  ``````
595
606
 
596
607
  ## Protecting Admin Routes
597
608
 
598
609
  ``````typescript
599
- // app/_layout.tsx
600
- import {useSelectCurrentUser} from "@/store/openApiSdk";
610
+ // app/admin/_layout.tsx
611
+ import {canOpenAdminPage} from "@terreno/syncdb/app";
612
+ import {useSession} from "@terreno/syncdb/auth";
601
613
  import {Redirect} from "expo-router";
614
+ import {useGetAuthMeQuery} from "@/store/apiSdk";
602
615
 
603
- function RootLayout() {
604
- const user = useSelectCurrentUser();
605
- const pathname = usePathname();
606
-
607
- if (!user?.admin && pathname.startsWith("/admin")) {
616
+ const AdminGate: React.FC<{children: React.ReactNode}> = ({children}) => {
617
+ const {userId} = useSession();
618
+ const {data: me, isLoading} = useGetAuthMeQuery(undefined, {skip: !userId});
619
+
620
+ if (isLoading) {
621
+ return null;
622
+ }
623
+ if (!canOpenAdminPage({admin: me?.admin, permissions: me?.permissions})) {
608
624
  return <Redirect href="/" />;
609
625
  }
610
-
611
- return <Stack />;
612
- }
626
+ return <>{children}</>;
627
+ };
613
628
  ``````
614
629
 
615
630
  ## Custom Field Renderers
@@ -664,10 +679,10 @@ handled as a generic model form:
664
679
  import {CommsDashboardScreenWidget, CommsMessageDetail} from "@terreno/admin-frontend";
665
680
 
666
681
  // list: /admin/comms
667
- <CommsDashboardScreenWidget api={api} config={config} routeBase="/admin" screenName="comms" />
682
+ <CommsDashboardScreenWidget config={config} routeBase="/admin" screenName="comms" />
668
683
 
669
684
  // detail: /admin/comms/[id]
670
- <CommsMessageDetail api={api} messageId={id} routeBase="/admin" />
685
+ <CommsMessageDetail messageId={id} routeBase="/admin" />
671
686
  ```
672
687
 
673
688
  The list screen persists filters in the URL (`channel`, `provider`, `status`, `errorClass`, `q`,
@@ -703,7 +718,7 @@ prompt** on `aiPrompt:create`, **Create evaluator** on `aiEvaluator:create`, and
703
718
  Dataset detail hides **Add item** without `aiDataset:update` and **Run experiment** without
704
719
  `aiExperiment:create`; dataset lists hide **Import** without
705
720
  `aiDataset:update`. Judge schema checks use the production prompt version, or the latest version
706
- when production is unset. Widgets do not couple to host Redux.
721
+ when production is unset. Widgets do not couple to host app state.
707
722
 
708
723
  `ai-prompts` lists prompts with a folder rail, search, type badge, latest vs production columns
709
724
  (tooltips), 7-day usage, and **Create prompt** when `aiPrompt:create` is allowed. The create modal
@@ -844,7 +859,7 @@ markdown renders as markdown), plus a **Reason** field and
844
859
  | In flight | Both buttons and the reason field are disabled until the decision returns, so it is sent once. |
845
860
 
846
861
  Requests use the host-bound admin request client (`AdminProvider` `getAuthHeaders` / `apiOrigin`),
847
- never RTK `injectEndpoints`. Test ids: `harness-approvals` (root), `harness-approvals-list`,
862
+ never a second request client. Test ids: `harness-approvals` (root), `harness-approvals-list`,
848
863
  `harness-approvals-empty`, `harness-approvals-error`, `harness-approvals-loading`,
849
864
  `harness-approvals-refresh`, `harness-approvals-notice`, `harness-approval-row-<id>-clickable`,
850
865
  `harness-approval-detail`, `harness-approval-payload`, `harness-approval-reason`,
@@ -858,7 +873,7 @@ never RTK `injectEndpoints`. Test ids: `harness-approvals` (root), `harness-appr
858
873
 
859
874
  ```typescript
860
875
  import {bindAdminRequest, HarnessApprovalInbox} from "@terreno/admin-frontend";
861
- import {baseUrl} from "@terreno/rtk";
876
+ import {baseUrl} from "@terreno/syncdb/config";
862
877
  import {Page} from "@terreno/ui";
863
878
 
864
879
  // getAuthHeaders: the host's `() => ({Authorization: "Bearer …"})`, as passed to AdminProvider.
@@ -46,17 +46,23 @@ admin-spa/
46
46
  appConfig.ts # AdminSpaAppConfig defaults + merge helper
47
47
  index.ts # Public exports
48
48
  app/ # Expo Router SPA (Better Auth + admin screens)
49
- store/ # Redux + Better Auth client
49
+ components/ # AppConfigGate, StoreProvider (session + request client), AdminGate
50
+ store/ # Admin window syncdb config
50
51
  dist/ # Pre-built static export (produced by build:web)
51
52
  ```
52
53
 
53
54
  Boot flow in the SPA: `AppConfigGate` fetches `${basePath}/app-config.json` →
54
- `StoreProvider` builds Better Auth + Redux → `AdminGate` redirects anonymous
55
+ `StoreProvider` builds a cookie-session Better Auth client and a same-origin
56
+ request client (`createApiClient({credentials: "same-origin"})` from
57
+ `@terreno/syncdb/api`), then mounts `SessionProvider` (`@terreno/syncdb/auth`) and
58
+ `ApiClientProvider` → `AdminGate` reads `useSession()` and redirects anonymous
55
59
  users to `/login` and non-admins to `/forbidden` → the authorized provider reads
56
- `/admin/config`. When config contains String-`_id` models with `adminBroadcast`
60
+ `/admin/config`. The SPA loads the signed-in profile (`/auth/me`, for operator
61
+ roles) with `useApiQuery` over that request client. It has no Redux store and no
62
+ `@terreno/rtk` dependency. When config contains String-`_id` models with `adminBroadcast`
57
63
  and `syncCollection`, it starts a same-origin cookie-authenticated syncdb client
58
64
  with those collections in window mode and forwards `useConflicts()` to the admin.
59
- ObjectId and non-broadcast models remain on RTK.
65
+ ObjectId and non-broadcast models use the cookie-bound `AdminProvider` fetch client.
60
66
 
61
67
  When the backend enables organizations, the bundled SPA exposes:
62
68