@terreno/mcp 57.3.0 → 57.4.0
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/README.md +7 -1
- package/dist/bootstrap.d.ts +0 -18
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +10 -2428
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
- package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
- package/dist/docs/resources/syncdb.md +4 -1
- package/dist/docs/upgrades/57.4.0.md +140 -0
- package/dist/docs/versioned/next/README.md +3 -0
- package/dist/docs/versioned/next/explanation/README.md +4 -1
- package/dist/docs/versioned/next/explanation/admin-interface.md +20 -13
- package/dist/docs/versioned/next/explanation/charts.md +29 -0
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/dist/docs/versioned/next/explanation/example-coverage.md +43 -0
- package/dist/docs/versioned/next/explanation/local-first-data.md +5 -2
- package/dist/docs/versioned/next/explanation/loop-engineering.md +9 -6
- package/dist/docs/versioned/next/explanation/model-router-actions.md +19 -2
- package/dist/docs/versioned/next/explanation/organizations.md +88 -0
- package/dist/docs/versioned/next/explanation/planning-plugins.md +114 -0
- package/dist/docs/versioned/next/explanation/positioning.md +3 -3
- package/dist/docs/versioned/next/explanation/repository-settings.md +6 -5
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
- package/dist/docs/versioned/next/explanation/source-rules.md +1 -1
- package/dist/docs/versioned/next/explanation/static-analysis.md +76 -16
- package/dist/docs/versioned/next/how-to/README.md +10 -0
- package/dist/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
- package/dist/docs/versioned/next/how-to/add-organizations.md +204 -0
- package/dist/docs/versioned/next/how-to/admin-add-model.md +9 -1
- package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
- package/dist/docs/versioned/next/how-to/audit-log.md +85 -0
- package/dist/docs/versioned/next/how-to/background-jobs.md +296 -0
- package/dist/docs/versioned/next/how-to/build-admin-screens.md +42 -13
- package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
- package/dist/docs/versioned/next/how-to/circleci.md +136 -27
- package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +4 -0
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
- package/dist/docs/versioned/next/how-to/github-actions-ci.md +4 -3
- package/dist/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
- package/dist/docs/versioned/next/how-to/in-app-notifications.md +147 -0
- package/dist/docs/versioned/next/how-to/install-agent-skills.md +24 -12
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
- package/dist/docs/versioned/next/how-to/product-announcements.md +155 -0
- package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +102 -0
- package/dist/docs/versioned/next/how-to/seed-a-database.md +7 -2
- package/dist/docs/versioned/next/implementationPlans/README.md +8 -5
- package/dist/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
- package/dist/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
- package/dist/docs/versioned/next/implementationPlans/announcements.md +289 -0
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
- package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
- package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
- package/dist/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
- package/dist/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
- package/dist/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
- package/dist/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
- package/dist/docs/versioned/next/implementationPlans/job-queues.md +330 -0
- package/dist/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
- package/dist/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
- package/dist/docs/versioned/next/implementationPlans/notification-center.md +185 -0
- package/dist/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/dist/docs/versioned/next/reference/README.md +4 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +83 -5
- package/dist/docs/versioned/next/reference/admin-config.md +7 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +178 -5
- package/dist/docs/versioned/next/reference/admin-spa.md +18 -1
- package/dist/docs/versioned/next/reference/ai.md +2 -0
- package/dist/docs/versioned/next/reference/announcements.md +179 -0
- package/dist/docs/versioned/next/reference/api.md +343 -4
- package/dist/docs/versioned/next/reference/comms.md +33 -0
- package/dist/docs/versioned/next/reference/create-terreno-app.md +44 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +29 -0
- package/dist/docs/versioned/next/reference/jobs.md +319 -0
- package/dist/docs/versioned/next/reference/legacy/rtk.md +5 -1
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
- package/dist/docs/versioned/next/reference/mcp-server.md +2 -43
- package/dist/docs/versioned/next/reference/scan-plugin.md +169 -0
- package/dist/docs/versioned/next/reference/syncdb.md +38 -12
- package/dist/docs/versioned/next/reference/test.md +57 -6
- package/dist/docs/versioned/next/reference/ui-performance.md +2 -1
- package/dist/docs/versioned/next/reference/ui.md +257 -3
- package/dist/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
- package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
- package/dist/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
- package/dist/docs/versioned/next/tasks/announcements.md +173 -0
- package/dist/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
- package/dist/docs/versioned/next/tasks/create-terreno-app.md +82 -0
- package/dist/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
- package/dist/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
- package/dist/docs/versioned/next/tasks/framework-audit-log.md +86 -0
- package/dist/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
- package/dist/docs/versioned/next/tasks/job-queues.md +144 -0
- package/dist/docs/versioned/next/tasks/knip-cleanup.md +146 -0
- package/dist/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
- package/dist/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
- package/dist/docs/versioned/next/tasks/notification-center.md +102 -0
- package/dist/docs/versioned/next/tasks/org-management-ui.md +14 -14
- package/dist/docs/versioned/next/tutorials/README.md +1 -1
- package/dist/docs/versioned/next/tutorials/getting-started.md +62 -22
- package/dist/help/excerpt.d.ts +2 -0
- package/dist/help/excerpt.d.ts.map +1 -0
- package/dist/help/excerpt.js +8 -0
- package/dist/help/excerpt.js.map +1 -0
- package/dist/help/updateNotes.d.ts +42 -0
- package/dist/help/updateNotes.d.ts.map +1 -0
- package/dist/help/updateNotes.js +235 -0
- package/dist/help/updateNotes.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/local/tools/databaseQuery.d.ts +1 -0
- package/dist/local/tools/databaseQuery.d.ts.map +1 -1
- package/dist/local/tools/databaseQuery.js +1 -0
- package/dist/local/tools/databaseQuery.js.map +1 -1
- package/dist/packageGuidelines.d.ts +2 -2
- package/dist/packageGuidelines.d.ts.map +1 -1
- package/dist/packageGuidelines.js +2 -8
- package/dist/packageGuidelines.js.map +1 -1
- package/dist/resources.d.ts +3 -0
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +3 -0
- package/dist/resources.js.map +1 -1
- package/dist/search/chunker.d.ts +1 -0
- package/dist/search/chunker.d.ts.map +1 -1
- package/dist/search/chunker.js +1 -0
- package/dist/search/chunker.js.map +1 -1
- package/dist/search/docIndex.d.ts +1 -5
- package/dist/search/docIndex.d.ts.map +1 -1
- package/dist/search/docIndex.js +1 -0
- package/dist/search/docIndex.js.map +1 -1
- package/dist/search/docVersion.d.ts +2 -1
- package/dist/search/docVersion.d.ts.map +1 -1
- package/dist/search/docVersion.js +3 -1
- package/dist/search/docVersion.js.map +1 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +113 -2
- package/dist/tools.js.map +1 -1
- package/package.json +28 -27
- package/src/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
- package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
- package/src/docs/resources/syncdb.md +4 -1
- package/src/docs/upgrades/57.4.0.md +140 -0
- package/src/docs/versioned/next/README.md +3 -0
- package/src/docs/versioned/next/explanation/README.md +4 -1
- package/src/docs/versioned/next/explanation/admin-interface.md +20 -13
- package/src/docs/versioned/next/explanation/charts.md +29 -0
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
- package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/src/docs/versioned/next/explanation/example-coverage.md +43 -0
- package/src/docs/versioned/next/explanation/local-first-data.md +5 -2
- package/src/docs/versioned/next/explanation/loop-engineering.md +9 -6
- package/src/docs/versioned/next/explanation/model-router-actions.md +19 -2
- package/src/docs/versioned/next/explanation/organizations.md +88 -0
- package/src/docs/versioned/next/explanation/planning-plugins.md +114 -0
- package/src/docs/versioned/next/explanation/positioning.md +3 -3
- package/src/docs/versioned/next/explanation/repository-settings.md +6 -5
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
- package/src/docs/versioned/next/explanation/source-rules.md +1 -1
- package/src/docs/versioned/next/explanation/static-analysis.md +76 -16
- package/src/docs/versioned/next/how-to/README.md +10 -0
- package/src/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
- package/src/docs/versioned/next/how-to/add-organizations.md +204 -0
- package/src/docs/versioned/next/how-to/admin-add-model.md +9 -1
- package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
- package/src/docs/versioned/next/how-to/audit-log.md +85 -0
- package/src/docs/versioned/next/how-to/background-jobs.md +296 -0
- package/src/docs/versioned/next/how-to/build-admin-screens.md +42 -13
- package/src/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
- package/src/docs/versioned/next/how-to/circleci.md +136 -27
- package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
- package/src/docs/versioned/next/how-to/configure-better-auth.md +4 -0
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
- package/src/docs/versioned/next/how-to/github-actions-ci.md +4 -3
- package/src/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
- package/src/docs/versioned/next/how-to/in-app-notifications.md +147 -0
- package/src/docs/versioned/next/how-to/install-agent-skills.md +24 -12
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
- package/src/docs/versioned/next/how-to/product-announcements.md +155 -0
- package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +102 -0
- package/src/docs/versioned/next/how-to/seed-a-database.md +7 -2
- package/src/docs/versioned/next/implementationPlans/README.md +8 -5
- package/src/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
- package/src/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
- package/src/docs/versioned/next/implementationPlans/announcements.md +289 -0
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
- package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
- package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
- package/src/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
- package/src/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
- package/src/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
- package/src/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
- package/src/docs/versioned/next/implementationPlans/job-queues.md +330 -0
- package/src/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
- package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
- package/src/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
- package/src/docs/versioned/next/implementationPlans/notification-center.md +185 -0
- package/src/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/src/docs/versioned/next/reference/README.md +4 -0
- package/src/docs/versioned/next/reference/admin-backend.md +83 -5
- package/src/docs/versioned/next/reference/admin-config.md +7 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +178 -5
- package/src/docs/versioned/next/reference/admin-spa.md +18 -1
- package/src/docs/versioned/next/reference/ai.md +2 -0
- package/src/docs/versioned/next/reference/announcements.md +179 -0
- package/src/docs/versioned/next/reference/api.md +343 -4
- package/src/docs/versioned/next/reference/comms.md +33 -0
- package/src/docs/versioned/next/reference/create-terreno-app.md +44 -0
- package/src/docs/versioned/next/reference/environment-variables.md +29 -0
- package/src/docs/versioned/next/reference/jobs.md +319 -0
- package/src/docs/versioned/next/reference/legacy/rtk.md +5 -1
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
- package/src/docs/versioned/next/reference/mcp-server.md +2 -43
- package/src/docs/versioned/next/reference/scan-plugin.md +169 -0
- package/src/docs/versioned/next/reference/syncdb.md +38 -12
- package/src/docs/versioned/next/reference/test.md +57 -6
- package/src/docs/versioned/next/reference/ui-performance.md +2 -1
- package/src/docs/versioned/next/reference/ui.md +257 -3
- package/src/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
- package/src/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
- package/src/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
- package/src/docs/versioned/next/tasks/announcements.md +173 -0
- package/src/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
- package/src/docs/versioned/next/tasks/create-terreno-app.md +82 -0
- package/src/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
- package/src/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
- package/src/docs/versioned/next/tasks/framework-audit-log.md +86 -0
- package/src/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
- package/src/docs/versioned/next/tasks/job-queues.md +144 -0
- package/src/docs/versioned/next/tasks/knip-cleanup.md +146 -0
- package/src/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
- package/src/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
- package/src/docs/versioned/next/tasks/notification-center.md +102 -0
- package/src/docs/versioned/next/tasks/org-management-ui.md +14 -14
- package/src/docs/versioned/next/tutorials/README.md +1 -1
- package/src/docs/versioned/next/tutorials/getting-started.md +62 -22
|
@@ -23,7 +23,7 @@ Local-first data layer for Terreno frontends. A TinyBase `MergeableStore` on dev
|
|
|
23
23
|
## Key exports
|
|
24
24
|
|
|
25
25
|
- `createSyncDb`, `SyncDb`, `SyncDbConfig`, `MutateArgs`
|
|
26
|
-
- `betterAuthAdapter`, `AuthProvider`
|
|
26
|
+
- `betterAuthAdapter`, `bridgeBetterAuthReactClient`, `AuthProvider`
|
|
27
27
|
- `listConflicts`, `wipeLocalData`, `generateMutationId`
|
|
28
28
|
- React (`@terreno/syncdb/react`): `SyncDbProvider`, `useEntity`, `useQuery`, `useEntityIds`, `useMutate`, `useSyncStatus`, `useConflicts`, `useSyncDebugLog`, `createCollectionHooks`
|
|
29
29
|
- CLI: `terreno-syncdb-codegen` (generates `SYNC_COLLECTIONS` + friendly hooks from OpenAPI)
|
|
@@ -108,6 +108,7 @@ const todoRouter = modelRouter("/todos", Todo, {
|
|
|
108
108
|
queryFilter: OwnerQueryFilter,
|
|
109
109
|
sync: {
|
|
110
110
|
scope: {type: "owner"}, // stream = todos|owner:{ownerId}
|
|
111
|
+
// adminBroadcast: true, // also emit sync:delta to todos|admin (default false)
|
|
111
112
|
},
|
|
112
113
|
});
|
|
113
114
|
```
|
|
@@ -126,7 +127,7 @@ new TerrenoApp({userModel: User})
|
|
|
126
127
|
|
|
127
128
|
- **`SyncApp`** — HTTP sync routes (`/sync/snapshot`, `/sync/mutate`, `/sync/mutate/batch`, `/sync/key`, `/sync/streams`, `/sync/entities`).
|
|
128
129
|
- **`RealtimeApp`** — Socket.io server with change-stream-driven `sync:delta` emission. Requires a **MongoDB replica set** (change streams). `modelRouter` `realtime` (RTK `sync` events) is deprecated and removed in Terreno 58; do not add it to new models.
|
|
129
|
-
- **`ensureSyncIndexes`** — `TerrenoApp.start()` awaits index builds for snapshot queries and sync bookkeeping (`SyncMutation.mutationId` unique, `SyncCounter.stream` unique, etc.). Registration queues this work without contacting MongoDB, so models can load before the database connects. Hosts that build Express without `TerrenoApp.start()` should await `ensureSyncIndexes()` after connecting.
|
|
130
|
+
- **`ensureSyncIndexes`** — `TerrenoApp.start()` binds the HTTP port first (or attaches to `httpServer` if the process already listened), then awaits index builds for snapshot queries and sync bookkeeping (`SyncMutation.mutationId` unique, `SyncCounter.stream` unique, etc.). Registration queues this work without contacting MongoDB, so models can load before the database connects. Hosts that build Express without `TerrenoApp.start()` should await `ensureSyncIndexes()` after connecting.
|
|
130
131
|
|
|
131
132
|
Socket auth requires at least one configured authentication method. It enables legacy JWT
|
|
132
133
|
validation when `tokenSecret` or `TOKEN_SECRET` is available. Better Auth can be used without
|
|
@@ -142,20 +143,29 @@ is the fallback.
|
|
|
142
143
|
## createSyncDb configuration
|
|
143
144
|
|
|
144
145
|
```typescript
|
|
145
|
-
import {betterAuthAdapter, createSyncDb} from "@terreno/syncdb";
|
|
146
|
+
import {betterAuthAdapter, bridgeBetterAuthReactClient, createSyncDb} from "@terreno/syncdb";
|
|
146
147
|
|
|
147
148
|
export const syncDb = createSyncDb({
|
|
148
149
|
name: "myapp",
|
|
149
150
|
collections: ["todos"],
|
|
150
|
-
authProvider: betterAuthAdapter(authClient),
|
|
151
|
+
authProvider: betterAuthAdapter(bridgeBetterAuthReactClient(authClient)),
|
|
151
152
|
baseUrl: "http://localhost:4000",
|
|
152
153
|
});
|
|
153
154
|
```
|
|
154
155
|
|
|
156
|
+
Wrap a Better Auth **react** client in `bridgeBetterAuthReactClient`. Its `useSession` is a React
|
|
157
|
+
hook rather than the `.subscribe` surface the adapter watches, so without the bridge the adapter
|
|
158
|
+
falls back to polling `getSession()` and produces constant `/api/auth/get-session` traffic. The
|
|
159
|
+
bridge exposes the client's `$store.atoms.session` atom and degrades to the polling fallback when
|
|
160
|
+
that atom is absent. Clients that already expose `getSession` plus a subscribable `useSession` can
|
|
161
|
+
be passed to `betterAuthAdapter` directly.
|
|
162
|
+
|
|
155
163
|
| Option | Type | Default | Description |
|
|
156
164
|
|--------|------|---------|-------------|
|
|
157
165
|
| `name` | `string` | — (required) | Persisted database name |
|
|
158
166
|
| `collections` | `string[]` | — (required) | Collection names to sync (local tables + subscriptions) |
|
|
167
|
+
| `windowCollections` | `string[]` | `[]` | Collections that join `{collection}\|admin`, skip snapshots/reconcile, and hydrate only known REST membership ids |
|
|
168
|
+
| `organizationIdProvider` | `() => string \| undefined` | — | Read at send time: sets `X-Organization-Id` on HTTP sync calls and `organizationId` on socket mutate payloads |
|
|
159
169
|
| `authProvider` | `AuthProvider` | — (required) | `{getToken, getUserId, onAuthChange, refresh?}` |
|
|
160
170
|
| `baseUrl` | `string` | — | Server origin; required unless both `transport` and `httpChannel` are injected |
|
|
161
171
|
| `transport` | `SyncTransport` | socket transport from `baseUrl` | Override for tests or custom wiring |
|
|
@@ -189,6 +199,7 @@ export const syncDb = createSyncDb({
|
|
|
189
199
|
| `goOnline()` | End simulated outage; reconnect triggers reconcile + outbox replay. |
|
|
190
200
|
| `mutate({collection, operation, id?, data?})` | Optimistic local write + durable outbox enqueue + fire-and-forget replay. Returns `{mutationId, id}`. |
|
|
191
201
|
| `reconcile()` | HTTP snapshot catch-up for every known stream; runs tombstone compaction on success. Also runs automatically on (re)connect, on a rate-limited seq-jump hint, and on the periodic timer; each `sync:subscribed` confirmation additionally pages just the streams it names. |
|
|
202
|
+
| `hydrateWindow({collection, ids, restRows?})` | Admin window upsert: REST rows (optional) land immediately; every requested id is also fetched from `GET /sync/entities` before this resolves so seq/deleted metadata is canonical for immediate update/delete. A `{collection}|admin` delta that lands while the fetch is in flight wins, so hydration never rewinds a row to an older seq. Unknown ids are ignored. |
|
|
192
203
|
| `forceResync()` | Purge every known stream locally and re-bootstrap from cursor 0 (outbox/conflicts untouched). Returns `{ok, reason?, streams, purged, repaired}`. |
|
|
193
204
|
| `replayOutbox()` | Drain queued mutations for the current user now. |
|
|
194
205
|
| `resolveConflict({mutationId, strategy})` | Apply `"useServer"` or `"keepMine"` to a recorded conflict. |
|
|
@@ -340,7 +351,7 @@ In React, prefer `useConflicts()`.
|
|
|
340
351
|
| Strategy | Behavior |
|
|
341
352
|
|----------|----------|
|
|
342
353
|
| `"useServer"` | Overwrite local entity with canonical server data/seq; clear pending state; discard conflicted outbox row. Writes a **tombstone** when the server side is deleted. |
|
|
343
|
-
| `"keepMine"` | Re-enqueue the mutation under a **fresh** `mutationId` with `baseVersion` set to the server's seq; local optimistic data is kept. |
|
|
354
|
+
| `"keepMine"` | Re-enqueue the mutation under a **fresh** `mutationId` with `baseVersion` set to the server's seq; local optimistic data is kept. All durable mutation metadata, including `mutationMode: "adminWindow"`, is preserved so retried admin-window writes receive the same RBAC, protected-field, hook, and audit handling. |
|
|
344
355
|
|
|
345
356
|
Resolve via `client.resolveConflict({mutationId, strategy})` or `useConflicts().resolve(...)`.
|
|
346
357
|
|
|
@@ -382,6 +393,7 @@ sync: {scope: {type: "owner"}} // todos|owner:{ownerId}
|
|
|
382
393
|
sync: {scope: {type: "owner", field: "userId"}} // todos|owner:{userId}
|
|
383
394
|
sync: {scope: {type: "tenant", field: "organizationId"}} // todos|tenant:{orgId}
|
|
384
395
|
sync: {scope: {type: "broadcast"}} // todos|all
|
|
396
|
+
sync: {scope: {type: "owner"}, adminBroadcast: true} // owner stream + todos|admin
|
|
385
397
|
sync: {
|
|
386
398
|
scope: (doc) => String(doc.workspaceId),
|
|
387
399
|
snapshotFilter: (user) => ({workspaceId: {$in: [...]}}), // required for custom
|
|
@@ -390,6 +402,16 @@ sync: {
|
|
|
390
402
|
|
|
391
403
|
- **Owner** streams use the authenticated socket's user id (client cannot pick another user's stream).
|
|
392
404
|
- **Tenant/custom** scopes resolve memberships via `SyncApp` `getUserScopes`.
|
|
405
|
+
- **`adminBroadcast`** (default `false`) is an additive fan-in flag on the existing collection `sync` config. When `true`, `sync:delta` emitters also publish to `{collection}|admin`. Do not change the app collection `scope` to broadcast for admin; app clients keep owner/tenant streams. Join `{collection}|admin` with `sync:subscribe {mode: "window"}`. The gate matches the admin UI shell: with `SyncApp({accessControl})`, the caller needs `admin:access`; without RBAC, `user.admin` / `Permissions.IsAdmin` is enough. When `AdminApp` is mounted it also registers that model's list/read permissions and `queryFilter`, so window subscribe, `GET /sync/entities`, and `|admin` live deltas cannot return rows `/admin` REST would hide (product `IsOwner` still treats `user.admin` as owner). Others get `sync:error`. The server confirms `sync:subscribed {mode: "window"}` and does not dump snapshot pages. Clients listed in `createSyncDb({windowCollections})` skip `GET /sync/snapshot` for those collections (startup, subscribe catch-up, and the reconcile timer). Hydrate known ids with `hydrateWindow` (REST rows + `GET /sync/entities`). For that HTTP lookup, an allowed admin-window caller on an `adminBroadcast` collection is not limited to owner/tenant stream membership. Deltas on `{collection}|admin` update or tombstone **ids already in the local window only**; unknown ids are ignored until Refresh or load-more hydrates them (Refresh = current REST query + `hydrateWindow`).
|
|
406
|
+
- `AdminApp({organizations: true})` forces `adminBroadcast: false` for models
|
|
407
|
+
with `organizationId`. The current admin-window socket protocol has no
|
|
408
|
+
selected-organization field, so those models use REST with
|
|
409
|
+
`X-Organization-Id` instead of weakening `OrgQueryFilter` or streaming
|
|
410
|
+
cross-organization rows.
|
|
411
|
+
- Use a dedicated `createSyncDb` client/store for an admin window when the same app
|
|
412
|
+
also subscribes to that collection through an owner or tenant stream. One socket
|
|
413
|
+
subscription has one mode per collection, and a separate store prevents
|
|
414
|
+
admin-hydrated rows from appearing in the product UI.
|
|
393
415
|
- **`snapshotFilter`** restricts `GET /sync/snapshot` server-side. Auto-derived for owner/tenant; required for custom resolver scopes.
|
|
394
416
|
|
|
395
417
|
## Sync protocol
|
|
@@ -400,9 +422,9 @@ sync: {
|
|
|
400
422
|
|----------|---------|
|
|
401
423
|
| `GET /sync/snapshot?collection=&stream=&cursor=&limit=` | Bootstrap + catch-up per stream |
|
|
402
424
|
| `GET /sync/streams` | Current stream membership for the user |
|
|
403
|
-
| `GET /sync/entities` | Point lookup for entity repair |
|
|
404
|
-
| `POST /sync/mutate` | Single mutation (HTTP fallback) |
|
|
405
|
-
| `POST /sync/mutate/batch` | Batched mutations (max 100, strict order, stop at first non-ack) |
|
|
425
|
+
| `GET /sync/entities` | Point lookup for entity repair and admin window hydrate. Admin-window callers (`admin:access` with RBAC, else `user.admin`) on `adminBroadcast` collections receive requested ids across product streams. When AdminApp registered a scope, list/read/`queryFilter` still apply; unknown or out-of-scope ids are omitted |
|
|
426
|
+
| `POST /sync/mutate` | Single mutation (HTTP fallback). Optional `mutationMode: "adminWindow"` validates `adminBroadcast`, admin-window access, and a registered AdminApp write scope, then runs the shared sync executor with **AdminApp** pre/post hooks (not product `modelRouter` hooks), plus the same permission (including org membership), stripping, and audit semantics as `/admin` REST. HTTP mutate binds `X-Organization-Id` into org context |
|
|
427
|
+
| `POST /sync/mutate/batch` | Batched mutations (max 100, strict order, stop at first non-ack). Each mutation may carry `mutationMode: "adminWindow"` under the same AdminApp executor hook path as single mutate |
|
|
406
428
|
| `GET /sync/key` | Per-user encryption key material (web) |
|
|
407
429
|
|
|
408
430
|
Conflict responses on mutate: **409** with `{nack}` body (`code: "conflict"`).
|
|
@@ -411,11 +433,11 @@ Conflict responses on mutate: **409** with `{nack}` body (`code: "conflict"`).
|
|
|
411
433
|
|
|
412
434
|
| Event | Direction | Payload |
|
|
413
435
|
|-------|-----------|---------|
|
|
414
|
-
| `sync:subscribe` / `sync:unsubscribe` | client → server | `{collections: string[]}` |
|
|
415
|
-
| `sync:subscribed` | server → client | `{collection, streams}` — sent after the stream rooms are joined;
|
|
436
|
+
| `sync:subscribe` / `sync:unsubscribe` | client → server | `{collections: string[], mode?: "window"}` |
|
|
437
|
+
| `sync:subscribed` | server → client | `{collection, streams, mode?: "window"}` — sent after the stream rooms are joined; full-mode clients page each confirmed stream from its cursor. `mode: "window"` joins `{collection}\|admin` only and must not trigger snapshot paging |
|
|
416
438
|
| `sync:error` | server → client | `{collection, message}` |
|
|
417
439
|
| `sync:delta` | server → client | `{collection, id, method, data?, seq, stream, deleted?, frontierSeq?}` |
|
|
418
|
-
| `sync:mutate` | client → server | `{mutationId, collection, operation, id?, data?, baseVersion?}` |
|
|
440
|
+
| `sync:mutate` | client → server | `{mutationId, collection, operation, id?, data?, baseVersion?, mutationMode?}` — validated `mutationMode: "adminWindow"` uses AdminApp executor hooks server-side |
|
|
419
441
|
| `sync:ack` | server → client | `{mutationId, id, seq}` |
|
|
420
442
|
| `sync:nack` | server → client | `{mutationId, code, serverDoc?, serverSeq?, serverDeleted?, message?, retryAfterMs?}` |
|
|
421
443
|
| `sync:mutateBatch` | client → server | `{mutations: SyncMutateRequest[], batchId?}` |
|
|
@@ -485,4 +507,8 @@ Backend sync requires a MongoDB replica set (`MONGO_URI` with `replicaSet=`) for
|
|
|
485
507
|
- **No `bulkWrite` / `updateMany` / `deleteMany`** on synced models — use per-document loops.
|
|
486
508
|
- **Do not write reserved tables** (`_outbox`, `_cursors`, `_conflicts`) directly — use `client.mutate`, hooks, and `resolveConflict`.
|
|
487
509
|
- **User switch wipes local data** — confirmed different-user login clears the previous user's store; bare logout/401 does not (INV-2).
|
|
488
|
-
- **Keep `@terreno/rtk` for non-synced routes** — generated OpenAPI hooks remain
|
|
510
|
+
- **Keep `@terreno/rtk` for non-synced routes** — generated OpenAPI hooks remain
|
|
511
|
+
the right tool for custom REST endpoints, auth, feature flags, and ObjectId
|
|
512
|
+
admin compatibility CRUD. Eligible admin String-`_id` collection CRUD uses
|
|
513
|
+
windowed syncdb; framework admin RPC uses its host-bound fetch client. See
|
|
514
|
+
[How to migrate from RTK to syncdb](../how-to/migrate-rtk-to-syncdb.md).
|
|
@@ -109,8 +109,54 @@ Defaults: `TOKEN_SECRET`, `TOKEN_ISSUER`, `REFRESH_TOKEN_SECRET`, `SESSION_SECRE
|
|
|
109
109
|
|
|
110
110
|
## Coverage gates
|
|
111
111
|
|
|
112
|
-
Package CI uses `scripts/check-coverage.ts`
|
|
113
|
-
|
|
112
|
+
Package CI uses `scripts/check-coverage.ts` (`bun run test:coverage`, default 95%
|
|
113
|
+
functions and lines) as the live gate. Bun 1.4.2+ may exit 1 on bunfig
|
|
114
|
+
`coverageThreshold` before isolated LCOV merges; the script continues when tests
|
|
115
|
+
reported `0 fail` and then enforces 95% on the merged report. Dedicated CircleCI
|
|
116
|
+
jobs (`api-ci`, `ai-ci`,
|
|
117
|
+
`rtk-ci`, `ui-ci`, `syncdb-ci`, `comms-ci`, `mcp-server-ci`, `admin-spa-ci`) run
|
|
118
|
+
that script. Published packages without a dedicated workflow
|
|
119
|
+
(`admin-backend`, `admin-frontend`, `api-health`, `feature-flags`, `@terreno/test`)
|
|
120
|
+
run the same lint, compile, and coverage commands via the parameterized
|
|
121
|
+
`packages-ci` job. Retained GitHub Actions twins stay in lockstep (`on: []`),
|
|
122
|
+
including `.github/workflows/packages-ci.yml`. Those twins run
|
|
123
|
+
`scripts/ci/check-new-file-coverage-lcov.sh` with `working-directory: .` and
|
|
124
|
+
pass `secrets.CODECOV_TOKEN` into `upload-codecov`.
|
|
125
|
+
|
|
126
|
+
Each of those jobs then uploads `coverage/lcov.info` to Codecov with a distinct
|
|
127
|
+
flag (`api`, `ui`, `rtk`, …) via `scripts/upload-codecov.sh`. The script
|
|
128
|
+
downloads the linux uploader over HTTPS and refuses to run it unless the
|
|
129
|
+
SHA-256 digest matches the pin in the script (`CODECOV_UPLOADER_SHA256`).
|
|
130
|
+
`codecov.yml` sets per-package flags, `target: auto` with a 1% threshold so
|
|
131
|
+
trivial deltas do not fail PRs, and PR comments for the coverage diff. Uploads
|
|
132
|
+
skip when `CODECOV_TOKEN` is unset. Maintainers set that token in CircleCI
|
|
133
|
+
project env and as a GitHub Actions secret. For a public repo, Codecov still
|
|
134
|
+
requires a token unless the org disables token authentication for public
|
|
135
|
+
repositories (see [Codecov tokens](https://docs.codecov.com/docs/codecov-tokens)).
|
|
136
|
+
|
|
137
|
+
Demo CI uses `scripts/check-demo-coverage.ts` to fail when a PascalCase component
|
|
138
|
+
exported from `ui/src/index.tsx` has neither a `demo/story-config` registration nor
|
|
139
|
+
an allowlist reason in `DEMO_COVERAGE_ALLOWLIST`. Run it from the repo root:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
bun run check:demo-coverage
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The `demo_lint_and_typecheck` CircleCI command (and the retained
|
|
146
|
+
`.github/workflows/ui-demo-ci.yml` job) runs the unit tests and this check after
|
|
147
|
+
the demo compiles. Add a story plus `demoConfig.tsx` registration for new
|
|
148
|
+
components, or an allowlist entry with a specific reason — not "hard to demo".
|
|
149
|
+
The allowlist is limited to shell/providers, React context objects, thin RN
|
|
150
|
+
list wrappers, subcomponents already exercised by a parent story
|
|
151
|
+
(Filter, Table, DateTimeField, HeightField, ConsentFormScreen), and
|
|
152
|
+
product surfaces that belong in `example-frontend` rather than the isolated
|
|
153
|
+
UI demo (announcements and in-app notifications).
|
|
154
|
+
Standalone picker sheets (`NumberPickerActionSheet`, `DecimalRangeActionSheet`)
|
|
155
|
+
have their own demo stories.
|
|
156
|
+
|
|
157
|
+
`demo/package.json` `test:ci` runs Bun tests, including a smoke suite that mounts every
|
|
158
|
+
registered `DemoConfig` demo and story through `renderWithTheme` from
|
|
159
|
+
`@terreno/ui`'s test utilities (`demo/storiesSmoke.test.tsx`).
|
|
114
160
|
|
|
115
161
|
Pull requests also run the `New file coverage` workflow. Every newly added workspace
|
|
116
162
|
`.ts` or `.tsx` implementation file must have at least 90% function coverage and 90%
|
|
@@ -118,10 +164,15 @@ line coverage. Test, spec, story, generated OpenAPI SDK, `dist`, isolated-test,
|
|
|
118
164
|
`src/types` type modules, demo `story-config/*.config.tsx`, and Expo Router route
|
|
119
165
|
files (`index`, `_layout`, `+not-found`, `[param]`, plus example recovery screens
|
|
120
166
|
`forgotPassword` / `resetPassword` / `verifyEmail`) are excluded. A new implementation
|
|
121
|
-
file that is absent from LCOV is treated as 0% covered.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
167
|
+
file that is absent from LCOV is treated as 0% covered. Package CI jobs that
|
|
168
|
+
already produced `coverage/lcov.info` run the 90% check against that report
|
|
169
|
+
(`--package` + `--lcov`) so the dedicated job does not rerun the suite.
|
|
170
|
+
When the dedicated job does rerun a package, it prefers colocated
|
|
171
|
+
`foo.test.ts` / `foo.test.tsx` next to each new `foo.ts` and falls back to
|
|
172
|
+
the package `bun test` paths only when a new file has no sibling test.
|
|
173
|
+
It compiles `@terreno/*` workspace dist deps only when the package imports
|
|
174
|
+
them. The gate still expands globs before spawn so Playwright `*.spec.ts`
|
|
175
|
+
files are not collected. A glob that matches no files is omitted.
|
|
125
176
|
|
|
126
177
|
Run the same check locally against a base commit:
|
|
127
178
|
|
|
@@ -137,8 +137,9 @@ time includes dependency evaluation in the Bun test harness. Lower is better.
|
|
|
137
137
|
|
|
138
138
|
Changes in this slice:
|
|
139
139
|
|
|
140
|
-
- Measured heavy optional widgets (`GPTChat`, `EmojiSelector`, `MarkdownEditor`, consent/admin tools, and related
|
|
140
|
+
- Measured heavy optional widgets (`GPTChat`, `EmojiSelector`, `MarkdownEditor`, consent/admin tools, `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, and related
|
|
141
141
|
exports) now cross lazy root boundaries so their implementation files stay off the cold root import path.
|
|
142
|
+
`DashboardGrid` stays eager.
|
|
142
143
|
- `MarkdownView`, `DataTable` header info, and `EmojiSelector` defer `react-native-markdown-display` and
|
|
143
144
|
`emoji-datasource` until first use.
|
|
144
145
|
- `RootImportRegression.test.tsx` and `bun run performance:imports` guard the public entrypoints.
|
|
@@ -4,11 +4,12 @@ React Native UI component library (a large component library). Layout (Box, Page
|
|
|
4
4
|
|
|
5
5
|
## Key exports
|
|
6
6
|
|
|
7
|
-
- Layout: `Box`, `Page`, `SplitPage`, `Card`
|
|
7
|
+
- Layout: `Box`, `Page`, `SplitPage`, `Card`, `DashboardGrid`
|
|
8
8
|
- Forms: `TextField`, `SelectField`, `DateTimeField`, `CheckBox`
|
|
9
|
-
- Display: `Text`, `Heading`, `Badge`, `DataTable`
|
|
9
|
+
- Display: `Text`, `Heading`, `Badge`, `DataTable`, `LineChart`, `BarChart`, `AreaChart`, `DonutChart`
|
|
10
10
|
- Actions: `Button`, `IconButton`, `Link`
|
|
11
11
|
- Feedback: `Spinner`, `Modal`, `Toast`
|
|
12
|
+
- Notifications: `NotificationBell`, `NotificationInbox`, `NotificationPreferences`
|
|
12
13
|
- Authentication: `SocialLoginButton`, `LoginScreen`, `SignUpScreen`
|
|
13
14
|
- Theming: `TerrenoProvider`, `useTheme`, custom icon registry (`icons` prop)
|
|
14
15
|
- **Type re-exports:** `StyleProp`, `ViewStyle` (re-exported from react-native to avoid version conflicts)
|
|
@@ -31,8 +32,9 @@ supported and is convenient when startup cost is not material:
|
|
|
31
32
|
import {Box, DataTable, Icon} from "@terreno/ui";
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
Heavy optional widgets (`GPTChat`, `EmojiSelector`, `MarkdownEditor`, consent flows, and related admin tools) are
|
|
35
|
+
Heavy optional widgets (`GPTChat`, `EmojiSelector`, `MarkdownEditor`, consent flows, `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, and related admin tools) are
|
|
35
36
|
re-exported from the root entry through lazy boundaries. Importing them from `@terreno/ui` stays type-compatible, but
|
|
37
|
+
their implementation modules load on first render instead of during the initial root import. `DashboardGrid` stays eager.
|
|
36
38
|
their implementation modules load on first render instead of during the initial root import. `MarkdownView` and
|
|
37
39
|
`DataTable` header info defer `react-native-markdown-display`; `EmojiSelector` defers `emoji-datasource` until open.
|
|
38
40
|
|
|
@@ -58,6 +60,115 @@ const customStyle: StyleProp<ViewStyle> = {
|
|
|
58
60
|
- Ensures type compatibility when passing styles to @terreno/ui components
|
|
59
61
|
- Simplifies imports (one package instead of two)
|
|
60
62
|
|
|
63
|
+
### DashboardGrid
|
|
64
|
+
|
|
65
|
+
Eager layout-only wrapping grid. Default columns `{sm: 1, md: 2, lg: 3}`. Children stay caller-supplied `Card`s. Cell width is `(rowWidth - gap × (columns - 1)) / columns` so flex `gap` does not wrap extra columns.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<DashboardGrid>
|
|
69
|
+
<Card>
|
|
70
|
+
<LineChart data={points} legendLabel="Signups" />
|
|
71
|
+
</Card>
|
|
72
|
+
</DashboardGrid>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### LineChart
|
|
76
|
+
|
|
77
|
+
Single-series line chart drawn with `react-native-svg`. Empty data shows `emptyText` (default `"No data"`). `loading` shows a `Spinner`. Press or hover a point for `{label}: {value}`.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<LineChart
|
|
81
|
+
data={[{label: "Mon", value: 3}, {label: "Tue", value: 5}]}
|
|
82
|
+
legendLabel="Signups"
|
|
83
|
+
testID="signups"
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Hit targets use `Box` `onClick`, so testIDs are `{testID}.point.{index}-clickable`.
|
|
88
|
+
|
|
89
|
+
### BarChart
|
|
90
|
+
|
|
91
|
+
Single-series bar chart on the same owned-SVG contract as `LineChart` (empty, loading, legend, tooltip).
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<BarChart
|
|
95
|
+
data={[{label: "Mon", value: 3}, {label: "Tue", value: 5}]}
|
|
96
|
+
legendLabel="Signups"
|
|
97
|
+
testID="signups-bar"
|
|
98
|
+
/>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### AreaChart
|
|
102
|
+
|
|
103
|
+
Filled area plus line on the same owned-SVG contract as `LineChart`.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<AreaChart
|
|
107
|
+
data={[{label: "Mon", value: 3}, {label: "Tue", value: 5}]}
|
|
108
|
+
legendLabel="Signups"
|
|
109
|
+
testID="signups-area"
|
|
110
|
+
/>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### DonutChart
|
|
114
|
+
|
|
115
|
+
One slice per `{label, value}` point. Per-slice `color` overrides the theme paint. Legend is one row per slice (`legendLabel` is ignored).
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<DonutChart
|
|
119
|
+
data={[{label: "Open", value: 3}, {color: "#112233", label: "Closed", value: 5}]}
|
|
120
|
+
testID="status-donut"
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Chart visual regression
|
|
125
|
+
|
|
126
|
+
`bun run ui:charts:compare` screenshots `http://localhost:8085/demo/chart-visual-gallery`
|
|
127
|
+
and diffs each fixture against `demo/rendered-snapshots/<id>.png`.
|
|
128
|
+
`bun run ui:charts:update-snapshots` rewrites those goldens. Fixture ids live in
|
|
129
|
+
`demo/chartVisual/fixtureCatalog.ts`. Operator steps:
|
|
130
|
+
[Compare rendered chart snapshots](../how-to/compare-chart-rendered-snapshots.md).
|
|
131
|
+
|
|
132
|
+
## Notification components
|
|
133
|
+
|
|
134
|
+
Presentational only — no syncdb import. Wire data from your app's sync layer.
|
|
135
|
+
These components are not in the isolated UI component demo; see `example-frontend`.
|
|
136
|
+
|
|
137
|
+
### `NotificationBell`
|
|
138
|
+
|
|
139
|
+
| Prop | Type | Description |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `unreadCount` | `number` | Badge hidden when `0` |
|
|
142
|
+
| `onPress` | `() => void` | Opens the inbox (host owns visibility) |
|
|
143
|
+
| `renderBadge` | `({unreadCount, testID}) => ReactNode` | Replaces the default unread-count badge |
|
|
144
|
+
| `renderIcon` | `({testID}) => ReactNode` | Replaces the default bell icon |
|
|
145
|
+
| `testID` | `string` | Default `notification-bell` |
|
|
146
|
+
|
|
147
|
+
Custom renderers keep the built-in 40×40 tap target, positioning, toggle callback, and
|
|
148
|
+
accessible unread-count label. `renderBadge` runs only when `unreadCount > 0`.
|
|
149
|
+
|
|
150
|
+
### `NotificationInbox`
|
|
151
|
+
|
|
152
|
+
List-only; wrap in `SideDrawer`, `Modal`, or a sheet in the host screen.
|
|
153
|
+
|
|
154
|
+
| Prop | Type | Description |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `items` | `NotificationInboxItem[]` | Rows to render |
|
|
157
|
+
| `isLoading` | `boolean` | Shows spinner |
|
|
158
|
+
| `onMarkRead` / `onMarkUnread` | `(item) => void` | Toggle `readAt` via syncdb |
|
|
159
|
+
| `onDismiss` | `(item) => void` | Archive the row (`archivedAt` via syncdb) |
|
|
160
|
+
| `onOpen` | `(item) => void` | Tap handler (e.g. Expo Router for `href`) |
|
|
161
|
+
|
|
162
|
+
`NotificationInboxItem.archived` is optional. Archived rows receive an `Archived` label
|
|
163
|
+
and do not expose dismiss or read-state actions.
|
|
164
|
+
|
|
165
|
+
### `NotificationPreferences`
|
|
166
|
+
|
|
167
|
+
| Prop | Type | Description |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `preferences` | `{inapp, mail, push, sms}` | Current toggles |
|
|
170
|
+
| `onChange` | `(channel, value) => void` | Per-channel updates |
|
|
171
|
+
|
|
61
172
|
## Component Behaviors
|
|
62
173
|
|
|
63
174
|
### Button Layout Behavior
|
|
@@ -78,6 +189,87 @@ Buttons automatically size to their content unless `fullWidth` is specified:
|
|
|
78
189
|
|
|
79
190
|
Internally, Button sets `alignSelf: 'flex-start'` when `fullWidth={false}` to prevent stretching in column layouts.
|
|
80
191
|
|
|
192
|
+
### TextField password visibility
|
|
193
|
+
|
|
194
|
+
`type="password"` masks the value and renders a show/hide eye control at the end of the field.
|
|
195
|
+
The control is uncontrolled — the field tracks whether the value is revealed and starts hidden.
|
|
196
|
+
A disabled field cannot be revealed.
|
|
197
|
+
|
|
198
|
+
``````typescript
|
|
199
|
+
<TextField
|
|
200
|
+
title="Password"
|
|
201
|
+
type="password"
|
|
202
|
+
value={password}
|
|
203
|
+
onChange={setPassword}
|
|
204
|
+
autoComplete="current-password"
|
|
205
|
+
/>
|
|
206
|
+
``````
|
|
207
|
+
|
|
208
|
+
Pass `showVisibilityToggle={false}` where revealing the value is unacceptable, such as a shared or
|
|
209
|
+
on-camera screen:
|
|
210
|
+
|
|
211
|
+
``````typescript
|
|
212
|
+
<TextField showVisibilityToggle={false} title="Password" type="password" value={password} onChange={setPassword} />
|
|
213
|
+
``````
|
|
214
|
+
|
|
215
|
+
`Field` with `type="password"` renders the same control, and so do `LoginScreen` and `SignUpScreen`
|
|
216
|
+
password fields.
|
|
217
|
+
|
|
218
|
+
The toggle's test id defaults to `{testID}.visibility-toggle`, and `testIDs.visibilityToggle`
|
|
219
|
+
overrides it:
|
|
220
|
+
|
|
221
|
+
| Element | test id |
|
|
222
|
+
| --- | --- |
|
|
223
|
+
| Input | `{testID}` |
|
|
224
|
+
| Label | `{testID}.label` |
|
|
225
|
+
| Error | `{testID}.error` |
|
|
226
|
+
| Helper | `{testID}.helper` |
|
|
227
|
+
| Show/hide toggle | `{testID}.visibility-toggle` |
|
|
228
|
+
|
|
229
|
+
### GPTChat
|
|
230
|
+
|
|
231
|
+
Streaming chat surface for `@terreno/ai`. Histories, messages, submit, and optional MCP/tools stay under consumer control.
|
|
232
|
+
|
|
233
|
+
Pass `mascot` when the app owns a character. Terreno ships none. The node renders only while `currentMessages` is empty, centered in the chat panel above the suggested prompts. The empty hero uses the message viewport height as a minimum so short content stays centered, while taller mascots still scroll. Streaming feedback shares that centered hero instead of creating a second pane. Omit the prop for the default empty chat.
|
|
234
|
+
|
|
235
|
+
The composer row (attachment picker, tools, input, Send) is vertically centered, so controls stay aligned with the input as it grows. The attachment cell is omitted when `onAttachFiles` is not provided.
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
<GPTChat
|
|
239
|
+
currentMessages={[]}
|
|
240
|
+
histories={histories}
|
|
241
|
+
mascot={
|
|
242
|
+
<Box alignItems="center">
|
|
243
|
+
<Heading size="lg">🦊</Heading>
|
|
244
|
+
</Box>
|
|
245
|
+
}
|
|
246
|
+
onCreateHistory={onCreateHistory}
|
|
247
|
+
onDeleteHistory={onDeleteHistory}
|
|
248
|
+
onSelectHistory={onSelectHistory}
|
|
249
|
+
onSubmit={onSubmit}
|
|
250
|
+
/>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Operator steps: [Add a GPT chat mascot](../how-to/add-gpt-chat-mascot.md). Demo story: `GPTChat` → `Mascot`.
|
|
254
|
+
The example AI screen demonstrates a consumer selecting one of four bundled mascot
|
|
255
|
+
images once per mount.
|
|
256
|
+
|
|
257
|
+
### SplitPage
|
|
258
|
+
|
|
259
|
+
Master-detail layout. Pass `listViewData` plus `renderListViewItem` for the list, and
|
|
260
|
+
`renderContent` for the detail pane. On large screens both panes stay visible. On small
|
|
261
|
+
screens the detail replaces the list until the user goes back.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import {SplitPage, Text} from "@terreno/ui";
|
|
265
|
+
|
|
266
|
+
<SplitPage
|
|
267
|
+
listViewData={[{id: "1", name: "Inbox"}]}
|
|
268
|
+
renderListViewItem={({item}) => <Text>{item.name}</Text>}
|
|
269
|
+
renderContent={(index) => <Text>{index === undefined ? "Select an item" : "Detail"}</Text>}
|
|
270
|
+
/>
|
|
271
|
+
```
|
|
272
|
+
|
|
81
273
|
### Page Back Navigation
|
|
82
274
|
|
|
83
275
|
Set `backButton` to render the standard header back arrow. By default it calls `router.back()`; provide `onBack` when the screen needs a deterministic destination instead of browser history.
|
|
@@ -574,6 +766,68 @@ import {TerrenoProvider} from "@terreno/ui";
|
|
|
574
766
|
</TerrenoProvider>
|
|
575
767
|
``````
|
|
576
768
|
|
|
769
|
+
## DataTable server-side filtering
|
|
770
|
+
|
|
771
|
+
`DataTable` is data-layer agnostic. Optional filter and search props emit a
|
|
772
|
+
modelRouter-shaped JavaScript object through `onQueryChange`. The parent merges
|
|
773
|
+
that object with `page`, `limit`, and `sort` before calling a list endpoint.
|
|
774
|
+
|
|
775
|
+
Import the pure helper when building params outside the component:
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
import {buildDataTableListQuery} from "@terreno/ui/dataTableListQuery";
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### Query contract
|
|
782
|
+
|
|
783
|
+
| UI | Wire param |
|
|
784
|
+
| --- | --- |
|
|
785
|
+
| Toolbar search (`search` + `searchFields`) | `$or: [{field: {$regex, $options: "i"}}, ...]` (user text escaped) |
|
|
786
|
+
| Text column filter | `{field: {$regex, $options: "i"}}` |
|
|
787
|
+
| Boolean column filter | `{field: true \| false}`; unset omits the key |
|
|
788
|
+
| Date range | `field_gte` / `field_lte` ISO strings; either bound may be sent alone |
|
|
789
|
+
| Number range | `{field: {$gte?, $lte?}}` |
|
|
790
|
+
| Choice (one value) | `{field: string}` scalar equality |
|
|
791
|
+
| Choice (many values) | `{field: {$in: string[]}}` |
|
|
792
|
+
| Choice **Empty** (optional fields) | `{field: {$in: ["__empty__"]}}` on the wire; server maps to `null` (matches missing and null) |
|
|
793
|
+
| Choice **Empty** + concrete | `{field: {$in: [...values, "__empty__"]}}` |
|
|
794
|
+
|
|
795
|
+
`onQueryChange` never includes `page`, `limit`, or `sort`. Search is debounced
|
|
796
|
+
(250ms, same delay as admin list search).
|
|
797
|
+
|
|
798
|
+
The server accepts only the documented nested operator keys. Text `$regex` values
|
|
799
|
+
must be escaped literals; executable patterns and extra Mongo operators are rejected.
|
|
800
|
+
|
|
801
|
+
Date range filters collect calendar days, so a range covers whole UTC days: **from**
|
|
802
|
+
opens the chosen day (`00:00:00.000Z`) and **to** closes it (`23:59:59.999Z`), which
|
|
803
|
+
keeps rows recorded later on the end day inside the range.
|
|
804
|
+
|
|
805
|
+
Pass `emptyContent` to keep the table header, search, and filter controls mounted
|
|
806
|
+
while showing an application-specific empty state below the header.
|
|
807
|
+
Use `additionalFilters` for declared server filters whose fields are not visible
|
|
808
|
+
columns; web shows one **More filters** popover and native includes them in the same sheet.
|
|
809
|
+
|
|
810
|
+
### Platform chrome
|
|
811
|
+
|
|
812
|
+
| Platform | Chrome |
|
|
813
|
+
| --- | --- |
|
|
814
|
+
| Web | Toolbar search + per-column `Filter` popovers (`column.filter`) |
|
|
815
|
+
| Native | Toolbar search + one **Filters** sheet (`Modal`) with the same fields |
|
|
816
|
+
|
|
817
|
+
Column headers use `Filter` with `iconOnly`, which renders a compact icon trigger
|
|
818
|
+
(24px at the default `triggerSize="sm"`, 32px with `triggerSize="default"`) instead
|
|
819
|
+
of a labeled button. Give it an accessible name with `triggerAccessibilityLabel`.
|
|
820
|
+
|
|
821
|
+
A single-column popover offers only its own **Clear**, so it needs no per-field clear.
|
|
822
|
+
The surfaces that host several filters at once — the **More filters** popover and the
|
|
823
|
+
native **Filters** sheet — add a per-field **Clear filter** for booleans, whose toggle
|
|
824
|
+
cannot otherwise express "unset".
|
|
825
|
+
|
|
826
|
+
Omit `column.filter`, `searchFields`, and the related callbacks to keep today's
|
|
827
|
+
sort/page-only table.
|
|
828
|
+
|
|
829
|
+
Demo: `FilterableDataTable` story in the component demo (`demo:start`, port 8085).
|
|
830
|
+
|
|
577
831
|
## Related Documentation
|
|
578
832
|
|
|
579
833
|
- [UI performance benchmarks](ui-performance.md)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Tasks: Admin scripts via durable jobs
|
|
2
|
+
|
|
3
|
+
**IP:** [admin-scripts-durable-jobs.md](../implementationPlans/admin-scripts-durable-jobs.md)
|
|
4
|
+
|
|
5
|
+
- [x] **Task 1**: `admin/script` adapter (`defineAdminScriptJob`, payload parse, dual-write `BackgroundTask`)
|
|
6
|
+
- [x] **Task 2**: `AdminApp` enqueue/cancel when `JobsApp` is registered; in-process fallback
|
|
7
|
+
- [x] **Task 3**: example-backend worker + API share `defineAdminScriptJob(adminScripts)`
|
|
8
|
+
- [x] **Task 4**: Docs (how-to jobs, jobs reference, admin-backend scripts)
|
|
9
|
+
- [x] **Task 5**: Example GCP runtime selects `GcpCloudTasksRunner` and verifies callback OIDC
|
|
10
|
+
- [x] **Task 6**: CD supplies `JOBS_RUNNER=gcp-cloud-tasks` and per-PR callback URLs
|