@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.
- package/CHANGELOG.md +43 -0
- package/README.md +57 -3
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +17 -16
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
- package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
- package/dist/docs/prompts/debug.md +6 -4
- package/dist/docs/prompts/screen.md +11 -17
- package/dist/docs/resources/overview.md +5 -3
- package/dist/docs/resources/patterns.md +157 -238
- package/dist/docs/resources/syncdb.md +44 -16
- package/dist/docs/templates/bootstrap/CLAUDE.template.md +2 -2
- package/dist/docs/templates/bootstrap/cursorrules.template.md +7 -5
- package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
- package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
- package/dist/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
- package/dist/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
- package/dist/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
- package/dist/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
- package/dist/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
- package/dist/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
- package/dist/docs/upgrades/58.0.0-beta.1.md +504 -0
- package/dist/docs/upgrades/58.0.0.md +495 -4
- package/dist/docs/versioned/next/README.md +2 -2
- package/dist/docs/versioned/next/approval-rubric.md +2 -2
- package/dist/docs/versioned/next/explanation/admin-interface.md +6 -5
- package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
- package/dist/docs/versioned/next/explanation/ai-workflows.md +5 -3
- package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
- package/dist/docs/versioned/next/explanation/authentication.md +62 -43
- package/dist/docs/versioned/next/explanation/dependency-management.md +1 -1
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/dist/docs/versioned/next/explanation/example-coverage.md +2 -2
- package/dist/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
- package/dist/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
- package/dist/docs/versioned/next/explanation/local-first-data.md +11 -9
- package/dist/docs/versioned/next/explanation/model-router-actions.md +1 -1
- package/dist/docs/versioned/next/explanation/modular-api-design.md +2 -2
- package/dist/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
- package/dist/docs/versioned/next/explanation/source-rules.md +2 -2
- package/dist/docs/versioned/next/explanation/typescript-configuration.md +2 -4
- package/dist/docs/versioned/next/explanation/versioning-policy.md +3 -3
- package/dist/docs/versioned/next/how-to/README.md +3 -2
- package/dist/docs/versioned/next/how-to/add-feature-flags.md +50 -25
- package/dist/docs/versioned/next/how-to/add-organizations.md +2 -1
- package/dist/docs/versioned/next/how-to/admin-custom-home.md +8 -2
- package/dist/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
- package/dist/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
- package/dist/docs/versioned/next/how-to/build-admin-screens.md +10 -8
- package/dist/docs/versioned/next/how-to/build-for-web.md +1 -1
- package/dist/docs/versioned/next/how-to/call-external-apis.md +2 -2
- package/dist/docs/versioned/next/how-to/circleci.md +12 -6
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +114 -60
- package/dist/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
- package/dist/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
- package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
- package/dist/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
- package/dist/docs/versioned/next/how-to/github-actions-ci.md +2 -2
- package/dist/docs/versioned/next/how-to/in-app-notifications.md +1 -1
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
- package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
- package/dist/docs/versioned/next/how-to/product-announcements.md +6 -3
- package/dist/docs/versioned/next/how-to/rate-limiting.md +38 -16
- package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -3
- package/dist/docs/versioned/next/how-to/set-up-social-login.md +1 -1
- package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
- package/dist/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
- package/dist/docs/versioned/next/how-to/upgrade-banner.md +5 -3
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
- package/dist/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
- package/dist/docs/versioned/next/how-to/websocket-integration.md +113 -402
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
- package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
- package/dist/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
- package/dist/docs/versioned/next/reference/README.md +1 -1
- package/dist/docs/versioned/next/reference/admin-frontend.md +85 -70
- package/dist/docs/versioned/next/reference/admin-spa.md +10 -4
- package/dist/docs/versioned/next/reference/ai.md +36 -4
- package/dist/docs/versioned/next/reference/announcements.md +3 -3
- package/dist/docs/versioned/next/reference/api.md +39 -24
- package/dist/docs/versioned/next/reference/cli.md +3 -4
- package/dist/docs/versioned/next/reference/environment-variables.md +16 -16
- package/dist/docs/versioned/next/reference/feature-flags.md +48 -7
- package/dist/docs/versioned/next/reference/jobs.md +2 -2
- package/dist/docs/versioned/next/reference/legacy/rtk.md +25 -600
- package/dist/docs/versioned/next/reference/mcp-server.md +441 -1158
- package/dist/docs/versioned/next/reference/syncdb.md +426 -16
- package/dist/docs/versioned/next/reference/test.md +28 -3
- package/dist/docs/versioned/next/reference/ui.md +132 -19
- package/dist/docs/versioned/next/tasks/native-module-baseline.md +8 -4
- package/dist/docs/versioned/next/tasks/rate-limiting.md +9 -2
- package/dist/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
- package/dist/docs/versioned/next/tasks/remove-rtk.md +155 -0
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/local/localTools.js +3 -54
- package/dist/local/localTools.js.map +1 -1
- package/dist/local/tools/runtime.d.ts +0 -21
- package/dist/local/tools/runtime.d.ts.map +1 -1
- package/dist/local/tools/runtime.js +0 -176
- package/dist/local/tools/runtime.js.map +1 -1
- package/dist/local/tools/syncDb.d.ts +0 -11
- package/dist/local/tools/syncDb.d.ts.map +1 -1
- package/dist/local/tools/syncDb.js +1 -5
- package/dist/local/tools/syncDb.js.map +1 -1
- package/dist/packageGuidelines.d.ts +3 -2
- package/dist/packageGuidelines.d.ts.map +1 -1
- package/dist/packageGuidelines.js +4 -11
- package/dist/packageGuidelines.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +17 -18
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +1 -8
- package/dist/resources.js.map +1 -1
- package/dist/search/chunker.d.ts +1 -1
- package/dist/search/chunker.d.ts.map +1 -1
- package/dist/search/inferPackages.d.ts +1 -1
- package/dist/search/inferPackages.js +5 -5
- package/dist/search/inferPackages.js.map +1 -1
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +126 -119
- package/dist/tools.js.map +1 -1
- package/package.json +6 -6
- package/src/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
- package/src/docs/guidelines/ui/guidelines/core.md +1 -1
- package/src/docs/prompts/debug.md +6 -4
- package/src/docs/prompts/screen.md +11 -17
- package/src/docs/resources/overview.md +5 -3
- package/src/docs/resources/patterns.md +157 -238
- package/src/docs/resources/syncdb.md +44 -16
- package/src/docs/templates/bootstrap/CLAUDE.template.md +2 -2
- package/src/docs/templates/bootstrap/cursorrules.template.md +7 -5
- package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
- package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
- package/src/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
- package/src/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
- package/src/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
- package/src/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
- package/src/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
- package/src/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
- package/src/docs/upgrades/58.0.0-beta.1.md +504 -0
- package/src/docs/upgrades/58.0.0.md +495 -4
- package/src/docs/versioned/next/README.md +2 -2
- package/src/docs/versioned/next/approval-rubric.md +2 -2
- package/src/docs/versioned/next/explanation/admin-interface.md +6 -5
- package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
- package/src/docs/versioned/next/explanation/ai-workflows.md +5 -3
- package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
- package/src/docs/versioned/next/explanation/authentication.md +62 -43
- package/src/docs/versioned/next/explanation/dependency-management.md +1 -1
- package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/src/docs/versioned/next/explanation/example-coverage.md +2 -2
- package/src/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
- package/src/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
- package/src/docs/versioned/next/explanation/local-first-data.md +11 -9
- package/src/docs/versioned/next/explanation/model-router-actions.md +1 -1
- package/src/docs/versioned/next/explanation/modular-api-design.md +2 -2
- package/src/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
- package/src/docs/versioned/next/explanation/source-rules.md +2 -2
- package/src/docs/versioned/next/explanation/typescript-configuration.md +2 -4
- package/src/docs/versioned/next/explanation/versioning-policy.md +3 -3
- package/src/docs/versioned/next/how-to/README.md +3 -2
- package/src/docs/versioned/next/how-to/add-feature-flags.md +50 -25
- package/src/docs/versioned/next/how-to/add-organizations.md +2 -1
- package/src/docs/versioned/next/how-to/admin-custom-home.md +8 -2
- package/src/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
- package/src/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
- package/src/docs/versioned/next/how-to/build-admin-screens.md +10 -8
- package/src/docs/versioned/next/how-to/build-for-web.md +1 -1
- package/src/docs/versioned/next/how-to/call-external-apis.md +2 -2
- package/src/docs/versioned/next/how-to/circleci.md +12 -6
- package/src/docs/versioned/next/how-to/configure-better-auth.md +114 -60
- package/src/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
- package/src/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
- package/src/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
- package/src/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
- package/src/docs/versioned/next/how-to/github-actions-ci.md +2 -2
- package/src/docs/versioned/next/how-to/in-app-notifications.md +1 -1
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
- package/src/docs/versioned/next/how-to/password-reset.md +1 -1
- package/src/docs/versioned/next/how-to/product-announcements.md +6 -3
- package/src/docs/versioned/next/how-to/rate-limiting.md +38 -16
- package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -3
- package/src/docs/versioned/next/how-to/set-up-social-login.md +1 -1
- package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
- package/src/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
- package/src/docs/versioned/next/how-to/upgrade-banner.md +5 -3
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
- package/src/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
- package/src/docs/versioned/next/how-to/websocket-integration.md +113 -402
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
- package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
- package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
- package/src/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
- package/src/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
- package/src/docs/versioned/next/reference/README.md +1 -1
- package/src/docs/versioned/next/reference/admin-frontend.md +85 -70
- package/src/docs/versioned/next/reference/admin-spa.md +10 -4
- package/src/docs/versioned/next/reference/ai.md +36 -4
- package/src/docs/versioned/next/reference/announcements.md +3 -3
- package/src/docs/versioned/next/reference/api.md +39 -24
- package/src/docs/versioned/next/reference/cli.md +3 -4
- package/src/docs/versioned/next/reference/environment-variables.md +16 -16
- package/src/docs/versioned/next/reference/feature-flags.md +48 -7
- package/src/docs/versioned/next/reference/jobs.md +2 -2
- package/src/docs/versioned/next/reference/legacy/rtk.md +25 -600
- package/src/docs/versioned/next/reference/mcp-server.md +441 -1158
- package/src/docs/versioned/next/reference/syncdb.md +426 -16
- package/src/docs/versioned/next/reference/test.md +28 -3
- package/src/docs/versioned/next/reference/ui.md +132 -19
- package/src/docs/versioned/next/tasks/native-module-baseline.md +8 -4
- package/src/docs/versioned/next/tasks/rate-limiting.md +9 -2
- package/src/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
- package/src/docs/versioned/next/tasks/remove-rtk.md +155 -0
- package/dist/docs/guidelines/rtk/guidelines/core.md +0 -26
- package/dist/docs/resources/rtk.md +0 -181
- package/dist/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
- package/dist/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
- package/dist/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
- package/dist/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
- package/dist/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
- package/dist/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
- package/dist/docs/templates/bootstrap/frontend/store/sdk.template.md +0 -80
- package/src/docs/guidelines/rtk/guidelines/core.md +0 -26
- package/src/docs/resources/rtk.md +0 -181
- package/src/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
- package/src/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
- package/src/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
- package/src/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
- package/src/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
- package/src/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
- 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
|
-
- [
|
|
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`
|
|
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
|
-
|
|
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"`
|
|
18
|
-
| Embedded app | `getAuthHeaders` that return `Authorization: Bearer …`, plus `apiOrigin` set to the API origin (`@terreno/
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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 `
|
|
84
|
-
|
|
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
|
|
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
|
|
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`
|
|
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`
|
|
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
|
|
406
|
-
models always use REST
|
|
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
|
|
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(
|
|
521
|
+
const {config, isLoading, error} = useAdminConfig("/admin");
|
|
527
522
|
``````
|
|
528
523
|
|
|
529
524
|
Returns model metadata from `{baseUrl}/config`.
|
|
530
525
|
|
|
531
|
-
###
|
|
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
|
-
|
|
538
|
-
`POST {routePath}/bulk-patch`.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
} =
|
|
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
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
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 {
|
|
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
|
-
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
if (
|
|
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
|
-
|
|
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
|
|
682
|
+
<CommsDashboardScreenWidget config={config} routeBase="/admin" screenName="comms" />
|
|
668
683
|
|
|
669
684
|
// detail: /admin/comms/[id]
|
|
670
|
-
<CommsMessageDetail
|
|
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
|
|
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
|
|
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/
|
|
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
|
-
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|