@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.
Files changed (250) hide show
  1. package/README.md +7 -1
  2. package/dist/bootstrap.d.ts +0 -18
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +10 -2428
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
  7. package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
  8. package/dist/docs/resources/syncdb.md +4 -1
  9. package/dist/docs/upgrades/57.4.0.md +140 -0
  10. package/dist/docs/versioned/next/README.md +3 -0
  11. package/dist/docs/versioned/next/explanation/README.md +4 -1
  12. package/dist/docs/versioned/next/explanation/admin-interface.md +20 -13
  13. package/dist/docs/versioned/next/explanation/charts.md +29 -0
  14. package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
  15. package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  16. package/dist/docs/versioned/next/explanation/example-coverage.md +43 -0
  17. package/dist/docs/versioned/next/explanation/local-first-data.md +5 -2
  18. package/dist/docs/versioned/next/explanation/loop-engineering.md +9 -6
  19. package/dist/docs/versioned/next/explanation/model-router-actions.md +19 -2
  20. package/dist/docs/versioned/next/explanation/organizations.md +88 -0
  21. package/dist/docs/versioned/next/explanation/planning-plugins.md +114 -0
  22. package/dist/docs/versioned/next/explanation/positioning.md +3 -3
  23. package/dist/docs/versioned/next/explanation/repository-settings.md +6 -5
  24. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
  25. package/dist/docs/versioned/next/explanation/source-rules.md +1 -1
  26. package/dist/docs/versioned/next/explanation/static-analysis.md +76 -16
  27. package/dist/docs/versioned/next/how-to/README.md +10 -0
  28. package/dist/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
  29. package/dist/docs/versioned/next/how-to/add-organizations.md +204 -0
  30. package/dist/docs/versioned/next/how-to/admin-add-model.md +9 -1
  31. package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
  32. package/dist/docs/versioned/next/how-to/audit-log.md +85 -0
  33. package/dist/docs/versioned/next/how-to/background-jobs.md +296 -0
  34. package/dist/docs/versioned/next/how-to/build-admin-screens.md +42 -13
  35. package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
  36. package/dist/docs/versioned/next/how-to/circleci.md +136 -27
  37. package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
  38. package/dist/docs/versioned/next/how-to/configure-better-auth.md +4 -0
  39. package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
  40. package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
  41. package/dist/docs/versioned/next/how-to/github-actions-ci.md +4 -3
  42. package/dist/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
  43. package/dist/docs/versioned/next/how-to/in-app-notifications.md +147 -0
  44. package/dist/docs/versioned/next/how-to/install-agent-skills.md +24 -12
  45. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
  46. package/dist/docs/versioned/next/how-to/product-announcements.md +155 -0
  47. package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
  48. package/dist/docs/versioned/next/how-to/run-tests-locally.md +102 -0
  49. package/dist/docs/versioned/next/how-to/seed-a-database.md +7 -2
  50. package/dist/docs/versioned/next/implementationPlans/README.md +8 -5
  51. package/dist/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
  52. package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
  53. package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
  54. package/dist/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
  55. package/dist/docs/versioned/next/implementationPlans/announcements.md +289 -0
  56. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
  57. package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
  58. package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
  59. package/dist/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
  60. package/dist/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
  61. package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
  62. package/dist/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
  63. package/dist/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
  64. package/dist/docs/versioned/next/implementationPlans/job-queues.md +330 -0
  65. package/dist/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
  66. package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
  67. package/dist/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
  68. package/dist/docs/versioned/next/implementationPlans/notification-center.md +185 -0
  69. package/dist/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
  70. package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  71. package/dist/docs/versioned/next/reference/README.md +4 -0
  72. package/dist/docs/versioned/next/reference/admin-backend.md +83 -5
  73. package/dist/docs/versioned/next/reference/admin-config.md +7 -0
  74. package/dist/docs/versioned/next/reference/admin-frontend.md +178 -5
  75. package/dist/docs/versioned/next/reference/admin-spa.md +18 -1
  76. package/dist/docs/versioned/next/reference/ai.md +2 -0
  77. package/dist/docs/versioned/next/reference/announcements.md +179 -0
  78. package/dist/docs/versioned/next/reference/api.md +343 -4
  79. package/dist/docs/versioned/next/reference/comms.md +33 -0
  80. package/dist/docs/versioned/next/reference/create-terreno-app.md +44 -0
  81. package/dist/docs/versioned/next/reference/environment-variables.md +29 -0
  82. package/dist/docs/versioned/next/reference/jobs.md +319 -0
  83. package/dist/docs/versioned/next/reference/legacy/rtk.md +5 -1
  84. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
  85. package/dist/docs/versioned/next/reference/mcp-server.md +2 -43
  86. package/dist/docs/versioned/next/reference/scan-plugin.md +169 -0
  87. package/dist/docs/versioned/next/reference/syncdb.md +38 -12
  88. package/dist/docs/versioned/next/reference/test.md +57 -6
  89. package/dist/docs/versioned/next/reference/ui-performance.md +2 -1
  90. package/dist/docs/versioned/next/reference/ui.md +257 -3
  91. package/dist/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
  92. package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
  93. package/dist/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
  94. package/dist/docs/versioned/next/tasks/announcements.md +173 -0
  95. package/dist/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
  96. package/dist/docs/versioned/next/tasks/create-terreno-app.md +82 -0
  97. package/dist/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
  98. package/dist/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
  99. package/dist/docs/versioned/next/tasks/framework-audit-log.md +86 -0
  100. package/dist/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
  101. package/dist/docs/versioned/next/tasks/job-queues.md +144 -0
  102. package/dist/docs/versioned/next/tasks/knip-cleanup.md +146 -0
  103. package/dist/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
  104. package/dist/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
  105. package/dist/docs/versioned/next/tasks/notification-center.md +102 -0
  106. package/dist/docs/versioned/next/tasks/org-management-ui.md +14 -14
  107. package/dist/docs/versioned/next/tutorials/README.md +1 -1
  108. package/dist/docs/versioned/next/tutorials/getting-started.md +62 -22
  109. package/dist/help/excerpt.d.ts +2 -0
  110. package/dist/help/excerpt.d.ts.map +1 -0
  111. package/dist/help/excerpt.js +8 -0
  112. package/dist/help/excerpt.js.map +1 -0
  113. package/dist/help/updateNotes.d.ts +42 -0
  114. package/dist/help/updateNotes.d.ts.map +1 -0
  115. package/dist/help/updateNotes.js +235 -0
  116. package/dist/help/updateNotes.js.map +1 -0
  117. package/dist/index.js +1 -1
  118. package/dist/index.js.map +1 -1
  119. package/dist/local/tools/databaseQuery.d.ts +1 -0
  120. package/dist/local/tools/databaseQuery.d.ts.map +1 -1
  121. package/dist/local/tools/databaseQuery.js +1 -0
  122. package/dist/local/tools/databaseQuery.js.map +1 -1
  123. package/dist/packageGuidelines.d.ts +2 -2
  124. package/dist/packageGuidelines.d.ts.map +1 -1
  125. package/dist/packageGuidelines.js +2 -8
  126. package/dist/packageGuidelines.js.map +1 -1
  127. package/dist/resources.d.ts +3 -0
  128. package/dist/resources.d.ts.map +1 -1
  129. package/dist/resources.js +3 -0
  130. package/dist/resources.js.map +1 -1
  131. package/dist/search/chunker.d.ts +1 -0
  132. package/dist/search/chunker.d.ts.map +1 -1
  133. package/dist/search/chunker.js +1 -0
  134. package/dist/search/chunker.js.map +1 -1
  135. package/dist/search/docIndex.d.ts +1 -5
  136. package/dist/search/docIndex.d.ts.map +1 -1
  137. package/dist/search/docIndex.js +1 -0
  138. package/dist/search/docIndex.js.map +1 -1
  139. package/dist/search/docVersion.d.ts +2 -1
  140. package/dist/search/docVersion.d.ts.map +1 -1
  141. package/dist/search/docVersion.js +3 -1
  142. package/dist/search/docVersion.js.map +1 -1
  143. package/dist/tools.d.ts +2 -2
  144. package/dist/tools.d.ts.map +1 -1
  145. package/dist/tools.js +113 -2
  146. package/dist/tools.js.map +1 -1
  147. package/package.json +28 -27
  148. package/src/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
  149. package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
  150. package/src/docs/resources/syncdb.md +4 -1
  151. package/src/docs/upgrades/57.4.0.md +140 -0
  152. package/src/docs/versioned/next/README.md +3 -0
  153. package/src/docs/versioned/next/explanation/README.md +4 -1
  154. package/src/docs/versioned/next/explanation/admin-interface.md +20 -13
  155. package/src/docs/versioned/next/explanation/charts.md +29 -0
  156. package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
  157. package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
  158. package/src/docs/versioned/next/explanation/example-coverage.md +43 -0
  159. package/src/docs/versioned/next/explanation/local-first-data.md +5 -2
  160. package/src/docs/versioned/next/explanation/loop-engineering.md +9 -6
  161. package/src/docs/versioned/next/explanation/model-router-actions.md +19 -2
  162. package/src/docs/versioned/next/explanation/organizations.md +88 -0
  163. package/src/docs/versioned/next/explanation/planning-plugins.md +114 -0
  164. package/src/docs/versioned/next/explanation/positioning.md +3 -3
  165. package/src/docs/versioned/next/explanation/repository-settings.md +6 -5
  166. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
  167. package/src/docs/versioned/next/explanation/source-rules.md +1 -1
  168. package/src/docs/versioned/next/explanation/static-analysis.md +76 -16
  169. package/src/docs/versioned/next/how-to/README.md +10 -0
  170. package/src/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
  171. package/src/docs/versioned/next/how-to/add-organizations.md +204 -0
  172. package/src/docs/versioned/next/how-to/admin-add-model.md +9 -1
  173. package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
  174. package/src/docs/versioned/next/how-to/audit-log.md +85 -0
  175. package/src/docs/versioned/next/how-to/background-jobs.md +296 -0
  176. package/src/docs/versioned/next/how-to/build-admin-screens.md +42 -13
  177. package/src/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
  178. package/src/docs/versioned/next/how-to/circleci.md +136 -27
  179. package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
  180. package/src/docs/versioned/next/how-to/configure-better-auth.md +4 -0
  181. package/src/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
  182. package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
  183. package/src/docs/versioned/next/how-to/github-actions-ci.md +4 -3
  184. package/src/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
  185. package/src/docs/versioned/next/how-to/in-app-notifications.md +147 -0
  186. package/src/docs/versioned/next/how-to/install-agent-skills.md +24 -12
  187. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
  188. package/src/docs/versioned/next/how-to/product-announcements.md +155 -0
  189. package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
  190. package/src/docs/versioned/next/how-to/run-tests-locally.md +102 -0
  191. package/src/docs/versioned/next/how-to/seed-a-database.md +7 -2
  192. package/src/docs/versioned/next/implementationPlans/README.md +8 -5
  193. package/src/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
  194. package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
  195. package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
  196. package/src/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
  197. package/src/docs/versioned/next/implementationPlans/announcements.md +289 -0
  198. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
  199. package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
  200. package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
  201. package/src/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
  202. package/src/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
  203. package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
  204. package/src/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
  205. package/src/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
  206. package/src/docs/versioned/next/implementationPlans/job-queues.md +330 -0
  207. package/src/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
  208. package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
  209. package/src/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
  210. package/src/docs/versioned/next/implementationPlans/notification-center.md +185 -0
  211. package/src/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
  212. package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  213. package/src/docs/versioned/next/reference/README.md +4 -0
  214. package/src/docs/versioned/next/reference/admin-backend.md +83 -5
  215. package/src/docs/versioned/next/reference/admin-config.md +7 -0
  216. package/src/docs/versioned/next/reference/admin-frontend.md +178 -5
  217. package/src/docs/versioned/next/reference/admin-spa.md +18 -1
  218. package/src/docs/versioned/next/reference/ai.md +2 -0
  219. package/src/docs/versioned/next/reference/announcements.md +179 -0
  220. package/src/docs/versioned/next/reference/api.md +343 -4
  221. package/src/docs/versioned/next/reference/comms.md +33 -0
  222. package/src/docs/versioned/next/reference/create-terreno-app.md +44 -0
  223. package/src/docs/versioned/next/reference/environment-variables.md +29 -0
  224. package/src/docs/versioned/next/reference/jobs.md +319 -0
  225. package/src/docs/versioned/next/reference/legacy/rtk.md +5 -1
  226. package/src/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
  227. package/src/docs/versioned/next/reference/mcp-server.md +2 -43
  228. package/src/docs/versioned/next/reference/scan-plugin.md +169 -0
  229. package/src/docs/versioned/next/reference/syncdb.md +38 -12
  230. package/src/docs/versioned/next/reference/test.md +57 -6
  231. package/src/docs/versioned/next/reference/ui-performance.md +2 -1
  232. package/src/docs/versioned/next/reference/ui.md +257 -3
  233. package/src/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
  234. package/src/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
  235. package/src/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
  236. package/src/docs/versioned/next/tasks/announcements.md +173 -0
  237. package/src/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
  238. package/src/docs/versioned/next/tasks/create-terreno-app.md +82 -0
  239. package/src/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
  240. package/src/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
  241. package/src/docs/versioned/next/tasks/framework-audit-log.md +86 -0
  242. package/src/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
  243. package/src/docs/versioned/next/tasks/job-queues.md +144 -0
  244. package/src/docs/versioned/next/tasks/knip-cleanup.md +146 -0
  245. package/src/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
  246. package/src/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
  247. package/src/docs/versioned/next/tasks/notification-center.md +102 -0
  248. package/src/docs/versioned/next/tasks/org-management-ui.md +14 -14
  249. package/src/docs/versioned/next/tutorials/README.md +1 -1
  250. 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; the client pages each confirmed stream from its cursor so a write landing between the startup snapshot and the join is not missed |
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 the right tool for custom REST endpoints, admin, and auth configuration. See [How to migrate from RTK to syncdb](../how-to/migrate-rtk-to-syncdb.md).
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` to enforce the package-wide thresholds
113
- declared in `bunfig.toml`.
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. The gate runs each package's `bun test` file arguments (or `src` / `*.test.ts`
122
- globs) so Playwright `*.spec.ts` files are not collected. Globs are expanded in
123
- the coverage process before `bun test` is spawned, because spawn does not pass
124
- them through a shell. A glob that matches no files is omitted.
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