@terreno/agent-plugin 57.21.0 → 58.0.0-beta.2
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +89 -0
- package/package.json +1 -1
- package/skills/building-admin-interfaces/SKILL.md +9 -6
- package/skills/building-admin-interfaces/references/nav-and-shell.md +1 -1
- package/skills/building-admin-interfaces/references/patterns-and-tools.md +4 -3
- package/skills/building-terreno-apps/SKILL.md +48 -34
- package/skills/building-terreno-apps/agents/openai.yaml +2 -2
- package/skills/building-terreno-apps/references/navigation.md +20 -14
- package/skills/building-terreno-apps/references/route-structure.md +26 -13
- package/skills/building-terreno-apps/references/screen-patterns.md +37 -54
- package/skills/generate-sdk/SKILL.md +87 -78
- package/skills/terreno-backend-api/references/model-router.md +0 -4
- package/skills/terreno-data-fetching/SKILL.md +83 -71
- package/skills/terreno-data-fetching/agents/openai.yaml +2 -2
- package/skills/terreno-data-fetching/references/auth-and-tokens.md +56 -60
- package/skills/terreno-data-fetching/references/realtime.md +26 -21
- package/skills/terreno-data-fetching/references/sdk-customization.md +31 -60
- package/skills/terreno-ui/SKILL.md +2 -2
- package/skills/terreno-ui/references/data-display.md +2 -0
- package/skills/upgrading-terreno/SKILL.md +13 -6
- package/skills/upgrading-terreno/references/ordering.md +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,95 @@ Upgrade notes for consumer action live in [`mcp-server/src/docs/upgrades/`](mcp-
|
|
|
20
20
|
|
|
21
21
|
Unreleased changes live in [`changelog/unreleased/`](changelog/unreleased/). Add one Markdown file per feature (see that directory's README) instead of editing this section.
|
|
22
22
|
|
|
23
|
+
## [58.0.0-beta.2] - 2026-11-03
|
|
24
|
+
|
|
25
|
+
Upgrade note: [`mcp-server/src/docs/upgrades/58.0.0-beta.2.md`](mcp-server/src/docs/upgrades/58.0.0-beta.2.md).
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
|
|
29
|
+
- Terreno 58 targets Expo SDK 58 (`~58.0.14`), React Native `0.88.0`, and React `19.3.0`. `@terreno/ui` peers `react-native` `0.88.0`. Native catalog moves with the SDK: `react-native-gesture-handler` `~3.3.0`, `react-native-reanimated` `4.7.1` + `react-native-worklets` `0.13.0`, `react-native-screens` `~4.30.0`, `react-native-webview` `14.0.1`, `@sentry/react-native` `~8.28.0`, `@shopify/flash-list` `2.3.2`, `@shopify/react-native-skia` `2.14.0`. Rebuild dev clients and store binaries.
|
|
30
|
+
- Expo Router 58: set `export const unstable_settings = {initialRouteName}` on the layout instead of passing `initialRouteName` to `<Slot>` / navigators. `SidebarNavigation` ignores the prop.
|
|
31
|
+
- Expo Router 58 static rendering: a custom `app/+html.tsx` must render `headNodes` and `bodyNodes` from `useServerDocumentContext()` (`expo-router/html`), or exported pages ship without their JavaScript. `example-frontend` is updated.
|
|
32
|
+
- React Native 0.88 generated types: type refs as `ViewInstance`, `TextInstance`, `ScrollViewInstance`, and `TextInputInstance` (`Box.scrollRef`, `TextField.inputRef`, `Text` `ref`).
|
|
33
|
+
- `create-terreno-app` scaffolds Expo `~58.0.14`, React Native `0.88.0`, and React `19.3.0`.
|
|
34
|
+
- The Terreno 58 native module baseline lands in `example-frontend` and `demo`: `@stripe/stripe-react-native` 0.81.0, `react-native-purchases` and `react-native-purchases-ui` 10.12.2, `expo-device`, `expo-crypto`, `expo-local-authentication`, `expo-system-ui`, and `expo-build-properties` (Android Kotlin 2.2.21 and `compileSdkVersion` 37 pins). Rebuild every dev client and store binary. `react-native-otp-verify` is left out because it does not support the New Architecture. The demo has a hidden **Native Modules** page that shows each module's version and whether the running binary links it. See the 58.0.0 upgrade note.
|
|
35
|
+
- `TerrenoApp` turns HTTP rate limiting on by default. Omitting `rateLimit` now mounts the limiter with the memory store (auth 20 / 15 min, api 600 / 15 min). Pass `rateLimit: false` to turn it off. On Cloud Run pass `rateLimit: {trustProxy: 1}` so unauthenticated traffic keys by client IP. See the 58.0.0 upgrade note.
|
|
36
|
+
- `@terreno/admin-frontend` no longer takes an RTK `api`. The `api` prop is removed from every admin component (`AdminProvider`, `AdminShellLayout`, `AdminHome`, `AdminModelList`, `AdminModelTable`, `AdminModelForm`, `AdminScreenRouter`, `ConfigurationScreen`, `DocumentStorageBrowser`, `ConsentHistory`, comms, jobs, org, consent, and announcement screens, and widget props), and admin hooks drop their `api` argument (`useAdminConfig(apiBase)`, `useAdminScripts(apiBase)`, `useAdminRoles(apiBase)`, `useConfigurationApi({basePath})`, `useDocumentStorageApi(basePath)`, `useConsentHistory(baseUrl)`, `useOrganizationsApi(basePath)`, `useCommsDashboardApi()`, `useJobsDashboardApi()`, and the `useAiObservability*Api()` hooks).
|
|
37
|
+
- `useAdminApi(api, routePath, modelName)` is removed. Use `useAdminModelApi(routePath, modelName)`, which keeps the same hook names and calls the same `/admin/{routePath}` URLs through the admin fetch client. `AdminApi`, `EndpointBuilder`, and admin `injectEndpoints` are gone; `@reduxjs/toolkit` and `react-redux` are no longer peers of admin-frontend.
|
|
38
|
+
- `AdminProvider` is now the required auth binding for every admin request, including ObjectId and API-only model CRUD. Cookie SPAs pass `credentials="same-origin"`; embedded apps pass `apiOrigin` and `getAuthHeaders`. Product screens that reuse admin components wrap them in `AdminProvider`. See the 58.0.0 upgrade guide.
|
|
39
|
+
- Removed the legacy RTK realtime path. Use `sync` on `modelRouter` with `@terreno/syncdb` for live collection updates. `RealtimeApp`, `SyncApp`, socket auth, `sync:delta`, and admin `admin:model.changed` events are unchanged. See the 58.0.0 upgrade note.
|
|
40
|
+
- `@terreno/api`: `modelRouter` no longer accepts `realtime` (now a TypeScript error), and the change stream no longer emits the `sync` websocket event. Removed `RealtimeConfig`, `RealtimeEvent`, `DocumentSubscription`, `QuerySubscription`, `registerRealtime`, `getRealtimeRegistry`, `findRegistryEntryByCollection`, `findRegistryEntryByRoutePath`, `updateRealtimeRegistryOptions`, `clearRealtimeRegistry`, `RealtimeRegistryEntry`, the query-subscription store (`addQuerySubscription`, `removeQuerySubscription`, `removeAllSocketQueries`, `getQuerySubscriptionsForCollection`, `computeQueryId`, `clearQueryStore`), and `MAX_MODEL_SUBSCRIPTIONS` / `MAX_DOCUMENT_SUBSCRIPTIONS` / `MAX_QUERY_SUBSCRIPTIONS`. Sockets no longer handle `subscribe:model`, `subscribe:document`, or `subscribe:query`.
|
|
41
|
+
- `@terreno/rtk`: removed `realtimeList`, `realtimeDocument`, `setRealtimeSocket`, `getRealtimeSocket`, `useSyncConnection`, and the `RealtimeEvent` type. The rest of `@terreno/rtk` (including `useSocketConnection` and `useRealtimeDebug`) is removed in the same release; see `remove-rtk.md`.
|
|
42
|
+
- Removed `@terreno/rtk` and every Redux dependency (`@reduxjs/toolkit`, `react-redux`, `redux-persist`, `@rtk-query/codegen-openapi`). `@terreno/rtk` is not published for 58. `@terreno/syncdb` is the only frontend data, request, and session layer. Follow `docs/how-to/migrate-rtk-to-syncdb.md`, which maps every removed export to its replacement, and the 58.0.0 upgrade note.
|
|
43
|
+
- `@terreno/syncdb` adds `@terreno/syncdb/api` (`createApiClient`, `ApiError`), request hooks in `@terreno/syncdb/react` (`ApiClientProvider`, `useApiQuery`, `useLazyApiQuery`, `useApiMutation`, `useSocketEvent`), `@terreno/syncdb/config` (`baseUrl`, `baseWebsocketsUrl`, `appHeaders`), `@terreno/syncdb/auth` (`createBetterAuthClient`, `SessionProvider`, `useSession`, `jwtAuthAdapter`, `useJwtAuth`, token-expiry helpers), `@terreno/syncdb/flags` (`useFeatureFlags`, `useTerrenoFeatureFlags` on the sync socket), `@terreno/syncdb/app` (`useUpgradeCheck`, `hasPermission`, `canOpenAdminPage`, `installDevConsoleLogger`, `useMCPTools`), and `@terreno/syncdb/buildNumber`.
|
|
44
|
+
- `terreno-syncdb-codegen --api-out` generates typed hooks for every non-synced OpenAPI operation (same names as the RTK codegen), replacing `@rtk-query/codegen-openapi`. `bun run sdk` writes `store/apiSdk.ts`.
|
|
45
|
+
- `@terreno/ui` `ConsentNavigator`, `AnnouncementNavigator`, `useConsentForms`, `useSubmitConsent`, `useAnnouncements`, and `useAcknowledgeAnnouncement` take a request `client` instead of an RTK `api`.
|
|
46
|
+
- `GptHistory` (`gpt/histories`) is a synced collection and keeps its ObjectId `_id` (no migration). `McpServiceToken` (`mcp/service-tokens`, read-only) is a synced collection with String ids; run `mcpServiceTokenStringIdsMigration` (`@terreno/api`) before deploying 58 frontends.
|
|
47
|
+
- `create-terreno-app`, the `terreno_bootstrap` prompt, and the `terreno_generate_screen` tool scaffold apps without Redux or `@terreno/rtk`. The terreno-mcp-local `get_rtk_state` tool, the MCP resource `terreno://docs/rtk`, and `terreno state --slice rtk` are removed; use `get_syncdb_state` and `terreno state`.
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- `DataTable` columns size themselves when `width` is omitted. Each auto column starts at the wider of its header and its content (sampled from at most 50 rows, measured with a canvas on web and estimated on native), clamped to 80–360px, and auto columns grow to fill the table's container. Columns that set `width` keep it. `DataTableColumn.width` is now optional.
|
|
52
|
+
- `@terreno/syncdb` `createSyncDb({idFormats})` mints 24-hex ObjectIds for a collection set to `"objectId"`, so a model that keeps Mongoose's default ObjectId `_id` can sync without migrating its ids. `mutate()` throws on an explicit non-hex id for such a collection. `generateObjectId` and the `SyncIdFormat` type are exported.
|
|
53
|
+
- Sync `validation` nacks carry an optional `reason`: `invalid_id` when an ObjectId-keyed collection gets a non-hex id (rejected before the mutation is claimed), and `id_collision` when a create names an `_id` another document holds. A collision never touches the existing document and its message never names the holder. The ledger records the reason (`SyncMutation.nackReason`) so a redelivery replays it.
|
|
54
|
+
- `GET /sync/entities` leaves out non-hex ids for an ObjectId-keyed model instead of answering 400, so a rejected create queued for repair cannot fail the rest of the repair batch.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- The repository's agent rules now generate with rulesync 17 (up from 9). The root rule `.rulesync/rules/00-root.md` now generates `AGENTS.md`, which Devin and other AGENTS.md readers load, in place of `.devin/rules/00-root.md`. Never edit `AGENTS.md` by hand: edit the root rule and run `bun run rules`. The examples that existed only in the old hand-edited `AGENTS.md` (syncdb and RTK SDK usage) moved into the root rule, so `CLAUDE.md`, the Cursor root rule, and Copilot instructions get them too. Links in the root rule now resolve from the repository root. Generated Copilot hooks use the portable `command` field. Generated Copilot subagents no longer list `agent/runSubagent` as their only tool, so they get every default tool. Generated skills carry their `license`, `disable-model-invocation`, and Devin `triggers` frontmatter.
|
|
59
|
+
- `@terreno/syncdb/auth` `SessionProvider`: `logout()` no longer clears the session before the server answers. The session stays in the state until `authClient.signOut()` finishes (`signOutTimeoutMs`, default 10 seconds), and then it is cleared. Meanwhile the new `isSigningOut` state (`useSession().isSigningOut`) is true; `isLoading` is left alone, so a navigator gated on it keeps its screen. Before, a login screen that opened at once could find the still-live session and sign the user straight back in. A sign-out that fails or times out checks the session first. A session that is still live, or a cookie or native session the server cannot be asked about, stays signed in, and `error` is set to `SIGN_OUT_FAILED_MESSAGE` ("Couldn't sign out. Check your connection and try again."). A web bearer client, which drops its token before the request leaves, ends signed out. The global `auth/logout` action (a refused session) now ends signed out even when the sign-out request fails. With `apisToReset` (57.17.0), `logout()` purges the cache when the session clears, not when it is dispatched: a purge at dispatch refetched every mounted screen mid-sign-out, and a refetch that could not reach the server answered 401 and signed the user out even though the sign-out failed. A failed sign-out keeps both the session and the cache. The global `auth/logout` action still purges at once (only the first of several in flight) and again when the session clears. A sign-in for another user that lands while a sign-out is in flight is kept; a sign-out that started signed out asks `getSession` first, so a restore of the session it revoked is cleared. Show `selectBetterAuthIsSigningOut` on the sign-out control and `selectBetterAuthError` after a failed sign-out.
|
|
60
|
+
|
|
61
|
+
### Removed
|
|
62
|
+
|
|
63
|
+
- `@terreno/ui` no longer exports the `Filter` and `FilterProps` aliases deprecated in 57.7.0. Import `DropdownPanel` and `DropdownPanelProps` instead. `FilterSelectMenu`, `FilterBoolean`, `FilterAccordion`, and `FilterChangesBadge` keep their names.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- `@terreno/syncdb`: logging out while `syncDb.start()` is still running no longer loses local writes or logs errors. `start()` now resolves the web encryption key from `GET /sync/key` before it loads the store, so the flush in `stop()` never needs the network after the session has ended. A `stop()` or `signOut()` call now cancels a `start()` that is running or queued ahead of it, so the transport never connects without a session. A failed key fetch is now retried on the next save instead of failing every save for the rest of the session.
|
|
68
|
+
|
|
69
|
+
## [58.0.0-beta.1] - 2026-11-02
|
|
70
|
+
|
|
71
|
+
Upgrade note: [`mcp-server/src/docs/upgrades/58.0.0-beta.1.md`](mcp-server/src/docs/upgrades/58.0.0-beta.1.md).
|
|
72
|
+
|
|
73
|
+
### Breaking
|
|
74
|
+
|
|
75
|
+
- Terreno 58 targets Expo SDK 58 (`~58.0.14`), React Native `0.88.0`, and React `19.3.0`. `@terreno/ui` peers `react-native` `0.88.0`. Native catalog moves with the SDK: `react-native-gesture-handler` `~3.3.0`, `react-native-reanimated` `4.7.1` + `react-native-worklets` `0.13.0`, `react-native-screens` `~4.30.0`, `react-native-webview` `14.0.1`, `@sentry/react-native` `~8.28.0`, `@shopify/flash-list` `2.3.2`, `@shopify/react-native-skia` `2.14.0`. Rebuild dev clients and store binaries.
|
|
76
|
+
- Expo Router 58: set `export const unstable_settings = {initialRouteName}` on the layout instead of passing `initialRouteName` to `<Slot>` / navigators. `SidebarNavigation` ignores the prop.
|
|
77
|
+
- Expo Router 58 static rendering: a custom `app/+html.tsx` must render `headNodes` and `bodyNodes` from `useServerDocumentContext()` (`expo-router/html`), or exported pages ship without their JavaScript. `example-frontend` is updated.
|
|
78
|
+
- React Native 0.88 generated types: type refs as `ViewInstance`, `TextInstance`, `ScrollViewInstance`, and `TextInputInstance` (`Box.scrollRef`, `TextField.inputRef`, `Text` `ref`).
|
|
79
|
+
- `create-terreno-app` scaffolds Expo `~58.0.14`, React Native `0.88.0`, and React `19.3.0`.
|
|
80
|
+
- The Terreno 58 native module baseline lands in `example-frontend` and `demo`: `@stripe/stripe-react-native` 0.81.0, `react-native-purchases` and `react-native-purchases-ui` 10.12.2, `expo-device`, `expo-crypto`, `expo-local-authentication`, `expo-system-ui`, and `expo-build-properties` (Android Kotlin 2.2.21 and `compileSdkVersion` 37 pins). Rebuild every dev client and store binary. `react-native-otp-verify` is left out because it does not support the New Architecture. The demo has a hidden **Native Modules** page that shows each module's version and whether the running binary links it. See the 58.0.0 upgrade note.
|
|
81
|
+
- `TerrenoApp` turns HTTP rate limiting on by default. Omitting `rateLimit` now mounts the limiter with the memory store (auth 20 / 15 min, api 600 / 15 min). Pass `rateLimit: false` to turn it off. On Cloud Run pass `rateLimit: {trustProxy: 1}` so unauthenticated traffic keys by client IP. See the 58.0.0 upgrade note.
|
|
82
|
+
- `@terreno/admin-frontend` no longer takes an RTK `api`. The `api` prop is removed from every admin component (`AdminProvider`, `AdminShellLayout`, `AdminHome`, `AdminModelList`, `AdminModelTable`, `AdminModelForm`, `AdminScreenRouter`, `ConfigurationScreen`, `DocumentStorageBrowser`, `ConsentHistory`, comms, jobs, org, consent, and announcement screens, and widget props), and admin hooks drop their `api` argument (`useAdminConfig(apiBase)`, `useAdminScripts(apiBase)`, `useAdminRoles(apiBase)`, `useConfigurationApi({basePath})`, `useDocumentStorageApi(basePath)`, `useConsentHistory(baseUrl)`, `useOrganizationsApi(basePath)`, `useCommsDashboardApi()`, `useJobsDashboardApi()`, and the `useAiObservability*Api()` hooks).
|
|
83
|
+
- `useAdminApi(api, routePath, modelName)` is removed. Use `useAdminModelApi(routePath, modelName)`, which keeps the same hook names and calls the same `/admin/{routePath}` URLs through the admin fetch client. `AdminApi`, `EndpointBuilder`, and admin `injectEndpoints` are gone; `@reduxjs/toolkit` and `react-redux` are no longer peers of admin-frontend.
|
|
84
|
+
- `AdminProvider` is now the required auth binding for every admin request, including ObjectId and API-only model CRUD. Cookie SPAs pass `credentials="same-origin"`; embedded apps pass `apiOrigin` and `getAuthHeaders`. Product screens that reuse admin components wrap them in `AdminProvider`. See the 58.0.0 upgrade guide.
|
|
85
|
+
- Removed the legacy RTK realtime path. Use `sync` on `modelRouter` with `@terreno/syncdb` for live collection updates. `RealtimeApp`, `SyncApp`, socket auth, `sync:delta`, and admin `admin:model.changed` events are unchanged. See the 58.0.0 upgrade note.
|
|
86
|
+
- `@terreno/api`: `modelRouter` no longer accepts `realtime` (now a TypeScript error), and the change stream no longer emits the `sync` websocket event. Removed `RealtimeConfig`, `RealtimeEvent`, `DocumentSubscription`, `QuerySubscription`, `registerRealtime`, `getRealtimeRegistry`, `findRegistryEntryByCollection`, `findRegistryEntryByRoutePath`, `updateRealtimeRegistryOptions`, `clearRealtimeRegistry`, `RealtimeRegistryEntry`, the query-subscription store (`addQuerySubscription`, `removeQuerySubscription`, `removeAllSocketQueries`, `getQuerySubscriptionsForCollection`, `computeQueryId`, `clearQueryStore`), and `MAX_MODEL_SUBSCRIPTIONS` / `MAX_DOCUMENT_SUBSCRIPTIONS` / `MAX_QUERY_SUBSCRIPTIONS`. Sockets no longer handle `subscribe:model`, `subscribe:document`, or `subscribe:query`.
|
|
87
|
+
- `@terreno/rtk`: removed `realtimeList`, `realtimeDocument`, `setRealtimeSocket`, `getRealtimeSocket`, `useSyncConnection`, and the `RealtimeEvent` type. The rest of `@terreno/rtk` (including `useSocketConnection` and `useRealtimeDebug`) is removed in the same release; see `remove-rtk.md`.
|
|
88
|
+
- Removed `@terreno/rtk` and every Redux dependency (`@reduxjs/toolkit`, `react-redux`, `redux-persist`, `@rtk-query/codegen-openapi`). `@terreno/rtk` is not published for 58. `@terreno/syncdb` is the only frontend data, request, and session layer. Follow `docs/how-to/migrate-rtk-to-syncdb.md`, which maps every removed export to its replacement, and the 58.0.0 upgrade note.
|
|
89
|
+
- `@terreno/syncdb` adds `@terreno/syncdb/api` (`createApiClient`, `ApiError`), request hooks in `@terreno/syncdb/react` (`ApiClientProvider`, `useApiQuery`, `useLazyApiQuery`, `useApiMutation`, `useSocketEvent`), `@terreno/syncdb/config` (`baseUrl`, `baseWebsocketsUrl`, `appHeaders`), `@terreno/syncdb/auth` (`createBetterAuthClient`, `SessionProvider`, `useSession`, `jwtAuthAdapter`, `useJwtAuth`, token-expiry helpers), `@terreno/syncdb/flags` (`useFeatureFlags`, `useTerrenoFeatureFlags` on the sync socket), `@terreno/syncdb/app` (`useUpgradeCheck`, `hasPermission`, `canOpenAdminPage`, `installDevConsoleLogger`, `useMCPTools`), and `@terreno/syncdb/buildNumber`.
|
|
90
|
+
- `terreno-syncdb-codegen --api-out` generates typed hooks for every non-synced OpenAPI operation (same names as the RTK codegen), replacing `@rtk-query/codegen-openapi`. `bun run sdk` writes `store/apiSdk.ts`.
|
|
91
|
+
- `@terreno/ui` `ConsentNavigator`, `AnnouncementNavigator`, `useConsentForms`, `useSubmitConsent`, `useAnnouncements`, and `useAcknowledgeAnnouncement` take a request `client` instead of an RTK `api`.
|
|
92
|
+
- `GptHistory` (`gpt/histories`) and `McpServiceToken` (`mcp/service-tokens`, read-only) are synced collections with String ids. Run the `gptHistoryStringIdsMigration` (`@terreno/ai`) and `mcpServiceTokenStringIdsMigration` (`@terreno/api`) before deploying 58 frontends.
|
|
93
|
+
- `create-terreno-app`, the `terreno_bootstrap` prompt, and the `terreno_generate_screen` tool scaffold apps without Redux or `@terreno/rtk`. The terreno-mcp-local `get_rtk_state` tool, the MCP resource `terreno://docs/rtk`, and `terreno state --slice rtk` are removed; use `get_syncdb_state` and `terreno state`.
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
|
|
97
|
+
- `DataTable` columns size themselves when `width` is omitted. Each auto column starts at the wider of its header and its content (sampled from at most 50 rows, measured with a canvas on web and estimated on native), clamped to 80–360px, and auto columns grow to fill the table's container. Columns that set `width` keep it. `DataTableColumn.width` is now optional.
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
|
|
101
|
+
- The repository's agent rules now generate with rulesync 17 (up from 9). The root rule `.rulesync/rules/00-root.md` now generates `AGENTS.md`, which Devin and other AGENTS.md readers load, in place of `.devin/rules/00-root.md`. Never edit `AGENTS.md` by hand: edit the root rule and run `bun run rules`. The examples that existed only in the old hand-edited `AGENTS.md` (syncdb and RTK SDK usage) moved into the root rule, so `CLAUDE.md`, the Cursor root rule, and Copilot instructions get them too. Links in the root rule now resolve from the repository root. Generated Copilot hooks use the portable `command` field. Generated Copilot subagents no longer list `agent/runSubagent` as their only tool, so they get every default tool. Generated skills carry their `license`, `disable-model-invocation`, and Devin `triggers` frontmatter.
|
|
102
|
+
- `@terreno/syncdb/auth` `SessionProvider`: `logout()` no longer clears the session before the server answers. The session stays in the state until `authClient.signOut()` finishes (`signOutTimeoutMs`, default 10 seconds), and then it is cleared. Meanwhile the new `isSigningOut` state (`useSession().isSigningOut`) is true; `isLoading` is left alone, so a navigator gated on it keeps its screen. Before, a login screen that opened at once could find the still-live session and sign the user straight back in. A sign-out that fails or times out checks the session first. A session that is still live, or a cookie or native session the server cannot be asked about, stays signed in, and `error` is set to `SIGN_OUT_FAILED_MESSAGE` ("Couldn't sign out. Check your connection and try again."). A web bearer client, which drops its token before the request leaves, ends signed out. The global `auth/logout` action (a refused session) now ends signed out even when the sign-out request fails. With `apisToReset` (57.17.0), `logout()` purges the cache when the session clears, not when it is dispatched: a purge at dispatch refetched every mounted screen mid-sign-out, and a refetch that could not reach the server answered 401 and signed the user out even though the sign-out failed. A failed sign-out keeps both the session and the cache. The global `auth/logout` action still purges at once (only the first of several in flight) and again when the session clears. A sign-in for another user that lands while a sign-out is in flight is kept; a sign-out that started signed out asks `getSession` first, so a restore of the session it revoked is cleared. Show `selectBetterAuthIsSigningOut` on the sign-out control and `selectBetterAuthError` after a failed sign-out.
|
|
103
|
+
|
|
104
|
+
### Removed
|
|
105
|
+
|
|
106
|
+
- `@terreno/ui` no longer exports the `Filter` and `FilterProps` aliases deprecated in 57.7.0. Import `DropdownPanel` and `DropdownPanelProps` instead. `FilterSelectMenu`, `FilterBoolean`, `FilterAccordion`, and `FilterChangesBadge` keep their names.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- `@terreno/syncdb`: logging out while `syncDb.start()` is still running no longer loses local writes or logs errors. `start()` now resolves the web encryption key from `GET /sync/key` before it loads the store, so the flush in `stop()` never needs the network after the session has ended. A `stop()` or `signOut()` call now cancels a `start()` that is running or queued ahead of it, so the transport never connects without a session. A failed key fetch is now retried on the next save instead of failing every save for the rest of the session.
|
|
111
|
+
|
|
23
112
|
## [57.21.0] - 2026-11-01
|
|
24
113
|
|
|
25
114
|
Upgrade note: [`mcp-server/src/docs/upgrades/57.21.0.md`](mcp-server/src/docs/upgrades/57.21.0.md).
|
package/package.json
CHANGED
|
@@ -37,9 +37,10 @@ and [Build admin screens](../../../../docs/how-to/build-admin-screens.md).
|
|
|
37
37
|
no per-model copy of the table exists.
|
|
38
38
|
5. UI: `@terreno/ui` + `AdminScreenPage` for custom screens; `color="transparent"`
|
|
39
39
|
inside the shell. String-`_id` models with `adminBroadcast` use windowed
|
|
40
|
-
syncdb;
|
|
41
|
-
|
|
42
|
-
`fetch`/`axios`
|
|
40
|
+
syncdb; ObjectId/API-only model CRUD uses `useAdminModelApi`; custom RPC uses
|
|
41
|
+
`useAdminRpc` + `useAdminRpcQuery`. All of it goes through the `AdminProvider`
|
|
42
|
+
fetch client. Done when there is no app-owned `fetch`/`axios` and no `api`
|
|
43
|
+
prop on admin components.
|
|
43
44
|
6. Verify with `verify-ui-changes` on the example admin or admin-spa. Done when
|
|
44
45
|
home, sidebar, and the new flow are exercised as an admin user.
|
|
45
46
|
|
|
@@ -56,11 +57,13 @@ references/
|
|
|
56
57
|
|
|
57
58
|
- Do not hand-roll a DataTable per model. Use `AdminModelTable` / `AdminScreenRouter`.
|
|
58
59
|
- Do not put a second sidebar inside a screen. `AdminShell` owns nav.
|
|
59
|
-
- Do not
|
|
60
|
-
|
|
60
|
+
- Do not pass an `api` prop to admin components (removed in Terreno 58).
|
|
61
|
+
`AdminProvider` (`credentials` for cookie SPAs, `apiOrigin` = `baseUrl` from
|
|
62
|
+
`@terreno/syncdb/config` + `getAuthHeaders` reading the app's
|
|
63
|
+
`authProvider.getToken()` for embedded apps) is the only auth binding.
|
|
61
64
|
- Do not invent sidebar entries. Config (filtered by RBAC) is the nav source.
|
|
62
65
|
- Do not use `router.back()` as the custom-screen back action; use `AdminScreenPage`.
|
|
63
|
-
- Do not edit `
|
|
66
|
+
- Do not edit generated `store/apiSdk.ts` / `store/syncDbSdk.ts` by hand.
|
|
64
67
|
|
|
65
68
|
## Decision tree
|
|
66
69
|
|
|
@@ -38,5 +38,5 @@ relocates those two into Platform.
|
|
|
38
38
|
## Auth gate
|
|
39
39
|
|
|
40
40
|
`admin:access` opens the page (`GET /admin/config` 403 without it). Hosts call
|
|
41
|
-
`canOpenAdminPage({admin, permissions})` from `@terreno/
|
|
41
|
+
`canOpenAdminPage({admin, permissions})` from `@terreno/syncdb/app` and hide product
|
|
42
42
|
nav to admin with the same check.
|
|
@@ -43,10 +43,11 @@ Reference: [admin-config](../../../../../docs/reference/admin-config.md).
|
|
|
43
43
|
## Data and MCP
|
|
44
44
|
|
|
45
45
|
- Admin String-`_id` collection CRUD: windowed syncdb with `adminBroadcast`.
|
|
46
|
-
-
|
|
47
|
-
-
|
|
46
|
+
- ObjectId/API-only model CRUD: `useAdminModelApi(routePath, modelName)`.
|
|
47
|
+
- Framework and custom admin RPC: `useAdminRpc` + `useAdminRpcQuery` /
|
|
48
|
+
`useAdminRpcMutation` over the `AdminProvider` client.
|
|
48
49
|
- After backend route changes: `cd example-frontend && bun run sdk` (non-synced
|
|
49
|
-
|
|
50
|
+
routes, `store/apiSdk.ts`) and `bun run sync-sdk` (synced collections).
|
|
50
51
|
- MCP: `terreno_install_admin` scaffolds wiring; still follow this skill for
|
|
51
52
|
screens and nav. `terreno_search_docs` / `terreno_get_component_docs` for APIs.
|
|
52
53
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
name: building-terreno-apps
|
|
3
3
|
description: >-
|
|
4
4
|
Complete guide for building Terreno full-stack Expo apps with @terreno/ui,
|
|
5
|
-
Expo Router, and @terreno/
|
|
5
|
+
Expo Router, and @terreno/syncdb. Covers layout, forms, navigation, theming,
|
|
6
6
|
screen patterns, and when to use Expo Go vs custom builds.
|
|
7
7
|
---
|
|
8
8
|
# Building Terreno Apps
|
|
9
9
|
|
|
10
|
-
Use this skill when building or refactoring screens in a Terreno Expo app (`example-frontend`, `admin-spa`, or a consumer app using `@terreno/ui` + `@terreno/
|
|
10
|
+
Use this skill when building or refactoring screens in a Terreno Expo app (`example-frontend`, `admin-spa`, or a consumer app using `@terreno/ui` + `@terreno/syncdb`). Terreno 58 apps have no Redux and no `@terreno/rtk`; upgrade old apps with `docs/how-to/migrate-rtk-to-syncdb.md`.
|
|
11
11
|
|
|
12
12
|
**Related skills:** `terreno-ui` (component selection), `terreno-data-fetching` (API hooks), `verify-ui-changes` (mandatory UI verification), `generate-sdk` (after backend changes), `building-admin-interfaces` (admin shell, custom screens, sidebar).
|
|
13
13
|
|
|
@@ -42,29 +42,43 @@ Admin CRUD, sidebar, custom screens, and `apiBase`/`routeBase`: use skill
|
|
|
42
42
|
|
|
43
43
|
## Provider Stack
|
|
44
44
|
|
|
45
|
-
Every Terreno app root layout
|
|
45
|
+
Every Terreno app root layout wraps children in this order (outer → inner). There is no
|
|
46
|
+
Redux store.
|
|
46
47
|
|
|
47
48
|
```tsx
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
49
|
+
import {SessionProvider} from "@terreno/syncdb/auth";
|
|
50
|
+
import {ApiClientProvider, SyncDbProvider} from "@terreno/syncdb/react";
|
|
50
51
|
import {TerrenoProvider} from "@terreno/ui";
|
|
51
|
-
|
|
52
|
-
import
|
|
52
|
+
import {betterAuthClient} from "@/lib/betterAuth";
|
|
53
|
+
import {apiClient} from "@/store/apiClient";
|
|
54
|
+
import {syncDb} from "@/store/syncdb";
|
|
53
55
|
|
|
54
56
|
const RootLayout: React.FC = () => {
|
|
55
57
|
return (
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
+
<SessionProvider authClient={betterAuthClient} syncDbs={[syncDb]} onLogout={() => apiClient.reset()}>
|
|
59
|
+
<ApiClientProvider client={apiClient}>
|
|
58
60
|
<TerrenoProvider>
|
|
59
|
-
<
|
|
61
|
+
<SyncDbProvider client={syncDb}>
|
|
62
|
+
<Stack />
|
|
63
|
+
</SyncDbProvider>
|
|
60
64
|
</TerrenoProvider>
|
|
61
|
-
</
|
|
62
|
-
</
|
|
65
|
+
</ApiClientProvider>
|
|
66
|
+
</SessionProvider>
|
|
63
67
|
);
|
|
64
68
|
};
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
| File | Holds |
|
|
72
|
+
|------|-------|
|
|
73
|
+
| `lib/betterAuth.ts` | `createBetterAuthClient` (`@terreno/syncdb/auth`) |
|
|
74
|
+
| `store/syncdb.ts` | Shared `authProvider` + `createSyncDb` client |
|
|
75
|
+
| `store/apiClient.ts` | `createApiClient` (`@terreno/syncdb/api`) for non-synced routes |
|
|
76
|
+
| `store/apiSdk.ts` | Generated request hooks — never edit |
|
|
77
|
+
| `store/syncDbSdk.ts` | Generated `SYNC_COLLECTIONS` + collection hooks — never edit |
|
|
78
|
+
|
|
79
|
+
See `example-frontend/app/_layout.tsx` for the full pattern including auth routing,
|
|
80
|
+
`syncDb.start()` on login, feature flags, consents, and announcements. App-wide UI state
|
|
81
|
+
uses React context or `useStoredState` from `@terreno/ui`.
|
|
68
82
|
|
|
69
83
|
## Code Style
|
|
70
84
|
|
|
@@ -119,20 +133,23 @@ Use the spacing scale via props (`padding={4}` → 16px). Prefer `gap` over marg
|
|
|
119
133
|
|
|
120
134
|
## Data on Screens
|
|
121
135
|
|
|
122
|
-
Never call `fetch` or `axios` in screen components.
|
|
136
|
+
Never call `fetch` or `axios` in screen components. Synced collections use syncdb hooks;
|
|
137
|
+
every other route uses the generated hooks in `store/apiSdk.ts`:
|
|
123
138
|
|
|
124
139
|
```tsx
|
|
125
|
-
import {
|
|
140
|
+
import {useMutate, useQuery} from "@terreno/syncdb/react";
|
|
141
|
+
import {useGetAuthMeQuery} from "@/store/apiSdk";
|
|
126
142
|
|
|
127
|
-
const
|
|
128
|
-
const
|
|
143
|
+
const todos = useQuery<Todo>("todos", {sort: "-created"});
|
|
144
|
+
const {create, update, remove} = useMutate("todos");
|
|
145
|
+
const {data: me, isLoading, error, refetch} = useGetAuthMeQuery();
|
|
129
146
|
```
|
|
130
147
|
|
|
131
148
|
See `terreno-data-fetching` for auth, caching, and error handling.
|
|
132
149
|
|
|
133
150
|
## Forms
|
|
134
151
|
|
|
135
|
-
Use `@terreno/ui` form fields with local state and
|
|
152
|
+
Use `@terreno/ui` form fields with local state. Synced writes apply locally and never throw:
|
|
136
153
|
|
|
137
154
|
```tsx
|
|
138
155
|
import {Button, TextField} from "@terreno/ui";
|
|
@@ -145,13 +162,9 @@ const handleSubmit = useCallback(async (): Promise<void> => {
|
|
|
145
162
|
setTitleError("Title is required");
|
|
146
163
|
return;
|
|
147
164
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} catch (err) {
|
|
152
|
-
console.error("Failed to create todo", err);
|
|
153
|
-
}
|
|
154
|
-
}, [createTodo, title]);
|
|
165
|
+
create({data: {_id: generateMutationId(), title: title.trim(), completed: false}});
|
|
166
|
+
setTitle("");
|
|
167
|
+
}, [create, title]);
|
|
155
168
|
|
|
156
169
|
<TextField
|
|
157
170
|
title="Title"
|
|
@@ -159,7 +172,7 @@ const handleSubmit = useCallback(async (): Promise<void> => {
|
|
|
159
172
|
onChange={setTitle}
|
|
160
173
|
errorText={titleError}
|
|
161
174
|
/>
|
|
162
|
-
<Button text="Create" onClick={handleSubmit}
|
|
175
|
+
<Button text="Create" onClick={handleSubmit} />
|
|
163
176
|
```
|
|
164
177
|
|
|
165
178
|
## Navigation
|
|
@@ -173,7 +186,8 @@ See `./references/navigation.md` and `./references/route-structure.md`.
|
|
|
173
186
|
|
|
174
187
|
## Loading, Error, and Empty States
|
|
175
188
|
|
|
176
|
-
Every data
|
|
189
|
+
Every screen that loads data over the request client must handle all three (synced
|
|
190
|
+
collections are local after bootstrap, so they need only the empty state):
|
|
177
191
|
|
|
178
192
|
```tsx
|
|
179
193
|
if (isLoading) {
|
|
@@ -187,7 +201,7 @@ if (error) {
|
|
|
187
201
|
</Page>
|
|
188
202
|
);
|
|
189
203
|
}
|
|
190
|
-
if (!data?.
|
|
204
|
+
if (!data?.length) {
|
|
191
205
|
return (
|
|
192
206
|
<Page title="Todos">
|
|
193
207
|
<Text>No todos yet</Text>
|
|
@@ -216,10 +230,10 @@ Building or changing a screen?
|
|
|
216
230
|
| \-- Page for full screens, Box for sections, Card for grouped content
|
|
217
231
|
|
|
|
218
232
|
|-- Need data from API?
|
|
219
|
-
| \-- terreno-data-fetching (
|
|
233
|
+
| \-- terreno-data-fetching (syncdb + generated apiSdk hooks, never raw fetch)
|
|
220
234
|
|
|
|
221
235
|
|-- Need a form?
|
|
222
|
-
| \-- @terreno/ui TextField/SelectField/DateTimeField + local state + mutation
|
|
236
|
+
| \-- @terreno/ui TextField/SelectField/DateTimeField + local state + useMutate / generated mutation
|
|
223
237
|
|
|
|
224
238
|
|-- Need navigation?
|
|
225
239
|
| \-- references/navigation.md (Expo Router + auth gating)
|
|
@@ -261,16 +275,16 @@ useEffect(() => {
|
|
|
261
275
|
}, []);
|
|
262
276
|
```
|
|
263
277
|
|
|
264
|
-
**Right:
|
|
278
|
+
**Right: syncdb hook for a synced collection**
|
|
265
279
|
|
|
266
280
|
```tsx
|
|
267
|
-
const
|
|
281
|
+
const todos = useQuery<Todo>("todos");
|
|
268
282
|
```
|
|
269
283
|
|
|
270
|
-
**Wrong: Editing
|
|
284
|
+
**Wrong: Editing store/apiSdk.ts by hand**
|
|
271
285
|
|
|
272
286
|
```tsx
|
|
273
|
-
// Manually adding a hook to store/
|
|
287
|
+
// Manually adding a hook to store/apiSdk.ts
|
|
274
288
|
```
|
|
275
289
|
|
|
276
290
|
**Right: Regenerate SDK after backend changes**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Building Terreno Apps"
|
|
3
|
-
short_description: "Build Terreno Expo apps with @terreno/ui layout,
|
|
4
|
-
default_prompt: "Use $building-terreno-apps to build or refactor a Terreno screen with @terreno/ui components, generated
|
|
3
|
+
short_description: "Build Terreno Expo apps with @terreno/ui layout, @terreno/syncdb data and session, Expo Router navigation, and mandatory UI verification"
|
|
4
|
+
default_prompt: "Use $building-terreno-apps to build or refactor a Terreno screen with @terreno/ui components, @terreno/syncdb hooks, generated store/apiSdk.ts hooks, and Expo Router navigation."
|
|
@@ -6,34 +6,40 @@ Terreno-specific navigation patterns.
|
|
|
6
6
|
|
|
7
7
|
## Auth flow
|
|
8
8
|
|
|
9
|
-
1. Root `_layout.tsx`
|
|
9
|
+
1. Root `_layout.tsx` reads `useSession()` from `@terreno/syncdb/auth`.
|
|
10
10
|
2. Unauthenticated users redirect to `/login`.
|
|
11
|
-
3. Login
|
|
12
|
-
4.
|
|
13
|
-
|
|
11
|
+
3. Login calls `betterAuthClient.signIn.email`, then `useSession().refresh()`.
|
|
12
|
+
4. Once `userId` is set, the root layout starts syncdb and redirects to `/(tabs)` or the
|
|
13
|
+
intended deep link.
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import {useSession} from "@terreno/syncdb/auth";
|
|
17
|
+
import {Box, Button, Page, TextField} from "@terreno/ui";
|
|
18
|
+
import {betterAuthClient} from "@/lib/betterAuth";
|
|
18
19
|
|
|
19
20
|
const LoginScreen: React.FC = () => {
|
|
21
|
+
const {refresh} = useSession();
|
|
20
22
|
const [email, setEmail] = useState<string>("");
|
|
21
23
|
const [password, setPassword] = useState<string>("");
|
|
22
|
-
const [
|
|
24
|
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
25
|
+
const [error, setError] = useState<string | undefined>();
|
|
23
26
|
|
|
24
27
|
const handleLogin = useCallback(async (): Promise<void> => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
const result = await betterAuthClient.signIn.email({email, password});
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
if (result.error) {
|
|
32
|
+
setError(result.error.message ?? "Login failed");
|
|
33
|
+
return;
|
|
29
34
|
}
|
|
30
|
-
|
|
35
|
+
await refresh();
|
|
36
|
+
}, [email, password, refresh]);
|
|
31
37
|
|
|
32
38
|
return (
|
|
33
39
|
<Page title="Login" scroll>
|
|
34
40
|
<Box gap={3} padding={4}>
|
|
35
41
|
<TextField title="Email" value={email} onChange={setEmail} type="email" />
|
|
36
|
-
<TextField title="Password" value={password} onChange={setPassword} type="password" />
|
|
42
|
+
<TextField title="Password" value={password} onChange={setPassword} type="password" errorText={error} />
|
|
37
43
|
<Button text="Sign in" onClick={handleLogin} loading={isLoading} fullWidth />
|
|
38
44
|
</Box>
|
|
39
45
|
</Page>
|
|
@@ -87,7 +93,7 @@ For confirmation dialogs and action sheets, prefer `@terreno/ui` `Modal` or `Act
|
|
|
87
93
|
|
|
88
94
|
## Deep linking
|
|
89
95
|
|
|
90
|
-
Configure `scheme` in `app.json`.
|
|
96
|
+
Configure `scheme` in `app.json`. `@terreno/syncdb/config` resolves API base URLs from `expo.extra.BASE_URL` or `EXPO_PUBLIC_API_URL` — keep deep link schemes separate from API URLs.
|
|
91
97
|
|
|
92
98
|
## Programmatic navigation
|
|
93
99
|
|
|
@@ -28,10 +28,13 @@ app/
|
|
|
28
28
|
[id].tsx # AdminModelForm (edit)
|
|
29
29
|
components/ # Screen components (NOT in app/)
|
|
30
30
|
hooks/ # Custom hooks
|
|
31
|
+
lib/
|
|
32
|
+
betterAuth.ts # createBetterAuthClient (@terreno/syncdb/auth)
|
|
31
33
|
store/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
syncdb.ts # Shared authProvider + createSyncDb client
|
|
35
|
+
apiClient.ts # createApiClient for non-synced routes
|
|
36
|
+
apiSdk.ts # Generated request hooks — never edit manually
|
|
37
|
+
syncDbSdk.ts # Generated collection hooks — never edit manually
|
|
35
38
|
```
|
|
36
39
|
|
|
37
40
|
### Rules
|
|
@@ -49,12 +52,15 @@ store/
|
|
|
49
52
|
|
|
50
53
|
```tsx
|
|
51
54
|
import {Redirect, Stack, useSegments} from "expo-router";
|
|
52
|
-
import {
|
|
55
|
+
import {useSession} from "@terreno/syncdb/auth";
|
|
53
56
|
|
|
54
57
|
const RootLayoutNav: React.FC = () => {
|
|
55
|
-
const userId =
|
|
58
|
+
const {userId, isLoading} = useSession();
|
|
56
59
|
const segments = useSegments();
|
|
57
60
|
|
|
61
|
+
if (isLoading) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
58
64
|
if (!userId && segments[0] !== "login") {
|
|
59
65
|
return <Redirect href="/login" />;
|
|
60
66
|
}
|
|
@@ -71,21 +77,28 @@ const RootLayoutNav: React.FC = () => {
|
|
|
71
77
|
Wrap admin routes once in `app/admin/_layout.tsx`:
|
|
72
78
|
|
|
73
79
|
```tsx
|
|
74
|
-
import {AdminShellLayout} from "@terreno/admin-frontend";
|
|
80
|
+
import {AdminProvider, AdminShellLayout} from "@terreno/admin-frontend";
|
|
81
|
+
import {baseUrl} from "@terreno/syncdb/config";
|
|
75
82
|
import {Stack} from "expo-router";
|
|
76
|
-
import {
|
|
83
|
+
import {getAdminAuthHeaders} from "@/store/syncdb"; // Bearer header from authProvider.getToken()
|
|
77
84
|
|
|
78
85
|
export default function AdminLayout(): React.ReactElement {
|
|
79
86
|
return (
|
|
80
|
-
<
|
|
81
|
-
api={terrenoApi}
|
|
87
|
+
<AdminProvider
|
|
82
88
|
apiBase="/admin"
|
|
83
|
-
|
|
89
|
+
apiOrigin={baseUrl}
|
|
90
|
+
getAuthHeaders={getAdminAuthHeaders}
|
|
84
91
|
routeBase="/admin"
|
|
85
|
-
versionConfigPath="/version-config"
|
|
86
92
|
>
|
|
87
|
-
<
|
|
88
|
-
|
|
93
|
+
<AdminShellLayout
|
|
94
|
+
apiBase="/admin"
|
|
95
|
+
configurationPath="/admin/configuration"
|
|
96
|
+
routeBase="/admin"
|
|
97
|
+
versionConfigPath="/version-config"
|
|
98
|
+
>
|
|
99
|
+
<Stack screenOptions={{headerShown: false, contentStyle: {flex: 1}}} />
|
|
100
|
+
</AdminShellLayout>
|
|
101
|
+
</AdminProvider>
|
|
89
102
|
);
|
|
90
103
|
}
|
|
91
104
|
```
|