@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
@@ -0,0 +1,296 @@
1
+ # Durable background jobs
2
+
3
+ Use `@terreno/jobs` when work must survive process restarts, retry with backoff, run on a
4
+ schedule, or dispatch through Cloud Tasks / Vercel Queues. Register `JobsApp`, define
5
+ handlers, enqueue rows in MongoDB, then start a worker explicitly.
6
+
7
+ For one-off **CLI** scripts or in-process timers, keep `@terreno/api` [`wrapScript` / `cronjob`](../reference/api.md#wrapscript) — they are process-local. Admin panel **HTTP** script runs go through `admin/script` when `JobsApp` is registered (see below). See [Jobs reference](../reference/jobs.md) for exports, routes, and defaults.
8
+
9
+ ## Install and register
10
+
11
+ ```bash
12
+ bun add @terreno/jobs
13
+ ```
14
+
15
+ Peer dependency: `mongoose ^8.0.0 || ^9.0.0`.
16
+
17
+ ```typescript
18
+ import {TerrenoApp} from "@terreno/api";
19
+ import {JobsApp, MongoJobRunner} from "@terreno/jobs";
20
+
21
+ const jobsApp = new JobsApp({
22
+ accessControl, // optional — requires admin:access AND admin:jobs when set
23
+ runner: new MongoJobRunner(),
24
+ timezone: "UTC",
25
+ });
26
+
27
+ jobsApp.define("billing/capture", {
28
+ handler: async (payload, ctx) => {
29
+ ctx.log.info("capturing", {invoiceId: (payload as {invoiceId?: string}).invoiceId});
30
+ // business logic
31
+ },
32
+ retry: {maxAttempts: 5, backoffMs: 1_000, maxBackoffMs: 900_000},
33
+ });
34
+
35
+ new TerrenoApp({userModel: User})
36
+ .register(jobsApp)
37
+ .start();
38
+ ```
39
+
40
+ `JobsApp.register()` wires admin routes and optional `POST /jobs/execute`. It does **not**
41
+ start a worker. Call `jobsApp.startWorker()` from the API process or a dedicated worker
42
+ entrypoint.
43
+
44
+ ## Admin scripts
45
+
46
+ When `JobsApp` is registered **before** `AdminApp`, `POST {adminBase}/scripts/:name/run`
47
+ enqueues job `admin/script` (`maxAttempts: 1`) and still returns `{taskId}` for the existing
48
+ Scripts poll UI (`BackgroundTask`). Progress, logs, dry/wet, and cancel stay on that row.
49
+ Cancel also cancels the job so `ctx.signal` aborts.
50
+
51
+ Apps without `JobsApp` keep the previous in-process `void` runner. The CLI
52
+ (`runScriptCli` / `bun run script`) stays process-local.
53
+
54
+ Register the same script catalog on the worker:
55
+
56
+ ```typescript
57
+ import {defineAdminScriptJob} from "@terreno/admin-backend";
58
+
59
+ defineAdminScriptJob(jobsApp, (name) => adminScripts.find((script) => script.name === name));
60
+ ```
61
+
62
+ `AdminApp` also binds `admin/script` from its `scripts` list at register time. Cloud Tasks
63
+ uses this path automatically when `JobsApp` is constructed with `GcpCloudTasksRunner` — the
64
+ worker/execute process must load the same script definitions. If runner `enqueue` throws,
65
+ the HTTP handler marks the `BackgroundTask` failed and returns 500 instead of leaving it
66
+ `pending`.
67
+
68
+ ## Enqueue
69
+
70
+ ```typescript
71
+ import {getJobsService} from "@terreno/jobs";
72
+ import {DateTime} from "luxon";
73
+
74
+ await getJobsService().enqueue({
75
+ name: "billing/capture",
76
+ payload: {invoiceId: "inv_123"},
77
+ });
78
+
79
+ await getJobsService().enqueue({
80
+ name: "billing/capture",
81
+ payload: {invoiceId: "inv_123"},
82
+ idempotencyKey: "capture:inv_123",
83
+ });
84
+
85
+ await getJobsService().enqueue({
86
+ name: "billing/capture",
87
+ payload: {invoiceId: "inv_123"},
88
+ runAt: DateTime.utc().plus({hours: 1}).toJSDate(),
89
+ });
90
+ ```
91
+
92
+ - Unknown `name` → `APIError` 400.
93
+ - Duplicate `(name, idempotencyKey)` returns the existing row and does **not** redispatch.
94
+ - `enqueue` persists first; the injected `JobRunner.enqueue` runs only for **new** rows.
95
+
96
+ ## In-process worker vs standalone worker
97
+
98
+ | Mode | When | Steps |
99
+ | --- | --- | --- |
100
+ | In-process | Local dev, single replica | After `TerrenoApp.start()`, call `jobsApp.startWorker()`. |
101
+ | Standalone | Production API + worker split | API: `JOBS_START_WORKER=false`. Run `bun run jobs:worker` (example-backend) in a second process with the same job definitions and Mongo URI. |
102
+
103
+ Example-backend gates the API-process worker with `JOBS_START_WORKER` (unset → `true`):
104
+
105
+ ```bash
106
+ # API only — no poll loop in the web process
107
+ JOBS_START_WORKER=false bun run backend:dev
108
+
109
+ # Dedicated worker (same env + Mongo as API)
110
+ bun run jobs:worker
111
+ ```
112
+
113
+ On shutdown, call `jobsApp.stopWorker()` so in-flight handlers receive `ctx.signal` abort.
114
+
115
+ ## Mongo runner: restart, retry, idempotency, schedules
116
+
117
+ **Restart:** Jobs are Mongo documents. A crashed worker leaves `running` rows; after
118
+ `lockTtlMs` (default 15 minutes) another worker reclaims them via `lockedAt` expiry.
119
+
120
+ **Retry:** Handler throws → `attemptCount++`, `attempts[]` entry, exponential backoff with
121
+ equal jitter, `status: "pending"` and future `runAt`. At `maxAttempts` (default 5) →
122
+ `status: "dead"`. Missing handler → `status: "failed"` (no automatic retry).
123
+
124
+ **Idempotency:** Optional `idempotencyKey` per `name` with a partial unique index. Use for
125
+ “enqueue once” semantics, not handler-side dedupe.
126
+
127
+ **Schedules:** Pass `schedule: {cron, timezone?}` on `define`. Worker `startWorker()` calls
128
+ `reconcileSchedules()`; the poll loop ticks due schedules, enqueues a child job, and skips a
129
+ tick when a child for that schedule is still `pending` / `running` / `scheduled`.
130
+
131
+ ## At-least-once handlers
132
+
133
+ Delivery is **at-least-once**: lock expiry, Cloud Task retries, or Vercel queue redelivery
134
+ can run a handler more than once.
135
+
136
+ - Make handlers idempotent (stable keys, upserts, “already processed” checks).
137
+ - Use `idempotencyKey` on enqueue to prevent duplicate **rows**, not duplicate execution after admin retry/requeue.
138
+ - Respect `ctx.signal` for cooperative cancellation.
139
+ - Use `ctx.log` (scoped logger) — `jobId` is in request context for log correlation.
140
+
141
+ ## Admin
142
+
143
+ `JobsApp.adminContribution()` registers:
144
+
145
+ - Custom screen `jobs` (dashboard)
146
+ - Home widget `jobs` (dead / running counts)
147
+
148
+ When `accessControl` is configured, callers need **both** `admin:access` and `admin:jobs`
149
+ (default `terrenoStatements.admin` includes both). When `accessControl` is omitted, routes
150
+ use legacy `user.admin === true`.
151
+
152
+ Frontend routes (example): `/admin/jobs`, `/admin/jobs/[id]`. Widgets ship in
153
+ `@terreno/admin-frontend` (`JOBS_ADMIN_WIDGETS`, `JOBS_HOME_WIDGETS`) — no host remap.
154
+ See [Import pre-built admins](admin-import-prebuilt.md).
155
+
156
+ Admin payloads are **hidden by default** (omit `redactPayload`). Opt in with a projection —
157
+ do not pass through the raw payload unless every field is safe for operators:
158
+
159
+ ```typescript
160
+ // Default — no hook; payload omitted from admin API responses
161
+ new JobsApp({accessControl});
162
+
163
+ // Opt-in — expose only non-sensitive fields
164
+ new JobsApp({
165
+ accessControl,
166
+ redactPayload: ({name, payload}) => {
167
+ if (name === "billing/capture") {
168
+ return {invoiceId: (payload as {invoiceId?: string}).invoiceId};
169
+ }
170
+ return undefined;
171
+ },
172
+ });
173
+ ```
174
+
175
+ ## GCP Cloud Tasks runner
176
+
177
+ Optional peer: `@google-cloud/tasks`.
178
+
179
+ ```typescript
180
+ import {GcpCloudTasksRunner} from "@terreno/jobs/runners/gcpCloudTasks";
181
+
182
+ const jobsApp = new JobsApp({
183
+ executeAuth: async (req) => {
184
+ // Verify OIDC token on POST /jobs/execute — your responsibility
185
+ return verifyGcpOidc(req);
186
+ },
187
+ runner: new GcpCloudTasksRunner({
188
+ project: "my-project",
189
+ location: "us-central1",
190
+ queue: "background-jobs",
191
+ publicUrl: "https://api.example.com",
192
+ serviceAccountEmail: "tasks-invoker@my-project.iam.gserviceaccount.com",
193
+ oidcAudience: "https://api.example.com/jobs/execute", // optional; defaults to execute URL
194
+ }),
195
+ });
196
+ ```
197
+
198
+ `GcpCloudTasksRunner` sets `requiresExecuteRoute: true`. Each enqueue creates an HTTP POST
199
+ to `{publicUrl}/jobs/execute` with body `{jobId}` (base64) and an OIDC token. Delayed jobs
200
+ use Cloud Tasks `scheduleTime`. HTTP tasks set `dispatchDeadline` to `{seconds: 1800}`
201
+ (REST JSON `"1800s"`) by default so Cloud Tasks does not retry at 10 minutes while a
202
+ 30-minute Cloud Run handler is still running. Override with `dispatchDeadlineSeconds`
203
+ (15–1800).
204
+ `@terreno/jobs` does **not** read `GCP_TASK_*` env vars —
205
+ pass explicit constructor config. Compiled `bun build --compile` binaries must pass a
206
+ `client` that does not load `@google-cloud/tasks` (that package reads
207
+ `cloud_tasks_client_config.json` from disk and is omitted from `$bunfs`). The example
208
+ backend POSTs to the Cloud Tasks REST API with `google-auth-library`.
209
+
210
+ The deployed example backend selects this runner with `JOBS_RUNNER=gcp-cloud-tasks`.
211
+ Infra Manager creates one queue, the API Cloud Run identity
212
+ `terreno-backend-runtime` (queue enqueuer + `actAs` on the callback SA), and a
213
+ callback-only `terreno-jobs-invoker` service account. The CD script supplies
214
+ the remaining `GCP_TASKS_*` values. Do not grant enqueue or `actAs` to the
215
+ project default Compute Engine SA. Both GitHub Actions and CircleCI deploy the same
216
+ runner configuration so either deploy path preserves Cloud Tasks execution. The API
217
+ process starts the worker so cron schedules
218
+ enqueue Cloud Tasks (`GcpCloudTasksRunner.start()` ticks Mongo schedules only). The tasks
219
+ Cloud Run service is the execution pool: Cloud Tasks pushes authenticated callbacks to it,
220
+ and queue rate limits bound concurrency. Keep `JOBS_START_WORKER=false` on the tasks
221
+ service so it does not double-tick schedules. Do not run `jobs:worker` in this mode —
222
+ that process would also tick schedules.
223
+
224
+ PR previews use the same queue but different callback URLs and databases. GitHub Actions
225
+ and CircleCI both deploy the `pr-<number>` tag on the tasks Cloud Run service before the
226
+ API preview starts enqueueing. GitHub Actions uses `env_vars_update_strategy: overwrite`
227
+ and `secrets_update_strategy: overwrite` (CircleCI `--set-env-vars`) so `MONGO_DB_NAME` /
228
+ `PR_NUMBER` and leftover secrets on a tagged revision do not merge into the next production
229
+ or preview deploy. The preview smoke container receives the same `JOBS_*` env as Cloud Run.
230
+ A task created by PR 123 targets that tag and reads
231
+ `terreno-example-pr-123`; it cannot execute against another PR or production.
232
+
233
+ ## Vercel Queues runner
234
+
235
+ Optional peer: `@vercel/queue`.
236
+
237
+ ```typescript
238
+ import {
239
+ createVercelQueuesConsumer,
240
+ VercelQueuesRunner,
241
+ } from "@terreno/jobs/runners/vercelQueues";
242
+
243
+ const jobsApp = new JobsApp({
244
+ runner: new VercelQueuesRunner({topic: "terreno-jobs"}),
245
+ });
246
+
247
+ // Private consumer route (platform auth via handleCallback — not Express execute route)
248
+ export const POST = createVercelQueuesConsumer({host: jobsApp});
249
+ ```
250
+
251
+ `vercel.json` must declare the queue trigger (private route):
252
+
253
+ ```json
254
+ {
255
+ "experimentalTriggers": [
256
+ {"type": "queue/v2beta", "topic": "terreno-jobs"}
257
+ ]
258
+ }
259
+ ```
260
+
261
+ **7-day limit:** `delaySeconds` and retention are capped at 604_800 seconds (7 days). `runAt`
262
+ beyond that window throws `VercelQueueRunAtBeyondLimitError` at enqueue — use a shorter delay
263
+ or a different runner for longer deferrals. Retention is bumped to cover `delaySeconds` when
264
+ needed.
265
+
266
+ `VercelQueuesRunner` does **not** mount `POST /jobs/execute`; the consumer calls
267
+ `executeJobById` through `createVercelQueuesConsumer`.
268
+
269
+ ## Custom runner
270
+
271
+ Implement `JobRunner`:
272
+
273
+ ```typescript
274
+ import type {JobRunner} from "@terreno/jobs";
275
+
276
+ const runner: JobRunner = {
277
+ id: "my-bus",
278
+ requiresExecuteRoute: false, // set true if you use POST /jobs/execute
279
+ async enqueue(job) {
280
+ await myBus.publish({jobId: job._id.toString()});
281
+ },
282
+ async start({jobs, signal, pollIntervalMs}) {
283
+ // optional — MongoJobRunner implements start; dispatch-only runners omit it
284
+ },
285
+ };
286
+ ```
287
+
288
+ Inject with `new JobsApp({runner})`. Dispatch failures on untouched `pending` rows roll back
289
+ the insert when possible (`JobDispatchError`).
290
+
291
+ ## Related
292
+
293
+ - [Jobs reference](../reference/jobs.md)
294
+ - [Environment variables — jobs](../reference/environment-variables.md#background-jobs)
295
+ - [Import pre-built admins](admin-import-prebuilt.md)
296
+ - Implementation plan: [job-queues.md](https://github.com/FlourishHealth/terreno/blob/master/docs/implementationPlans/job-queues.md)
@@ -22,7 +22,18 @@ Embedded app (`example-frontend`):
22
22
 
23
23
  ```tsx
24
24
  // app/admin/_layout.tsx
25
- <AdminProvider api={api} apiBase="/admin" routeBase="/admin" widgets={{screens: {"sync-lab": SyncLabScreen}}}>
25
+ const syncConflicts = useConflicts();
26
+
27
+ <AdminProvider
28
+ api={api}
29
+ apiBase="/admin"
30
+ apiOrigin={baseUrl}
31
+ getAuthHeaders={getAdminAuthHeaders}
32
+ routeBase="/admin"
33
+ syncConflicts={syncConflicts}
34
+ syncDb={adminSyncDb}
35
+ widgets={{screens: {"sync-lab": SyncLabScreen}}}
36
+ >
26
37
  <AdminShellLayout
27
38
  api={api}
28
39
  apiBase="/admin"
@@ -36,11 +47,25 @@ Embedded app (`example-frontend`):
36
47
  </AdminProvider>
37
48
  ```
38
49
 
39
- Gate entry with `canOpenAdminPage` from `@terreno/rtk` (`admin:access`). Do not
40
- rely on `user.admin` alone when RBAC is on.
50
+ Render this tree inside `SyncDbProvider client={adminSyncDb}`. If product screens
51
+ also sync the same collection, make `adminSyncDb` a dedicated client with a unique
52
+ store name and `windowCollections` set; do not mix admin rows into the owner/tenant
53
+ product store. Start that client after `canOpenAdminPage` succeeds, block the admin
54
+ shell until `start()` resolves, and call `stop()` when the admin layout unmounts.
41
55
 
42
- Standalone SPA (`admin-spa`): `routeBase=""`, `apiBase="/admin"`. Each route
43
- wraps `AdminShellLayout` because the root layout is only providers.
56
+ Gate entry with `canOpenAdminPage` from `@terreno/rtk` (`admin:access`). Do not
57
+ rely on `user.admin` alone when RBAC is on. Import `baseUrl` from `@terreno/rtk`
58
+ and pass it as `apiOrigin` so admin RPC (`GET /admin/config`, `/rbac/roles`, …)
59
+ hits the API instead of the Expo web origin.
60
+
61
+ Standalone SPA (`admin-spa`): `routeBase=""`, `apiBase="/admin"`, `credentials="same-origin"`,
62
+ and `getAuthHeaders` that return `{}` so the cookie session is sent without a Bearer header.
63
+ Omit `apiOrigin`. Each route wraps `AdminShellLayout` because the root layout is only
64
+ providers. Model tables render **Create** once, in the table chrome
65
+ (`testID="admin-create-button"`), because admin Expo stacks use `headerShown: false`.
66
+ Model forms render **Save** / **Delete** in the form chrome
67
+ (`testID="admin-save-button"` / `admin-delete-button`) for the same reason.
68
+ The navigator header only receives the screen title.
44
69
 
45
70
  ## 3. Keep generic model routes generic
46
71
 
@@ -53,9 +78,11 @@ app/admin/
53
78
  [model]/[id].tsx # <AdminModelForm />
54
79
  ```
55
80
 
56
- `AdminScreenRouter` resolves the URL segment as: `__scripts` → script list, matching
57
- model name → table, matching custom-screen name → `widgets.screens[name]`, else
58
- not-found. Do not copy `AdminModelTable` into a new file per model.
81
+ `AdminScreenRouter` resolves the URL segment as: `__scripts` → script list,
82
+ `__migrations` → migrations status (when `GET /admin/config` has
83
+ `migrations.enabled`), matching model name → table, matching custom-screen
84
+ name → `widgets.screens[name]`, else not-found. Do not copy `AdminModelTable`
85
+ into a new file per model.
59
86
 
60
87
  Create paths are `[model]/create`, not `new`.
61
88
 
@@ -96,10 +123,11 @@ for those paths. Otherwise `[model]/[id]` treats the id as a generic document.
96
123
 
97
124
  ## 5. Fetch data the admin way
98
125
 
99
- - Lists and forms: `useAdminApi(api, apiBase, modelName)` — never `axios` / `fetch`.
100
- - Config and nav: `useAdminConfig(api, apiBase)`.
101
- - Non-CRUD admin HTTP: generated SDK hooks after `bun run sdk`.
102
- - Do **not** put admin collections on `@terreno/syncdb`. Admin is server-first RTK.
126
+ - String-`_id` collections configured with `adminBroadcast` use windowed syncdb
127
+ when the host passes `syncDb` plus fetch auth.
128
+ - ObjectId model CRUD and API-only hosts keep `useAdminApi`.
129
+ - Bulk patch remains server-side: native admin fetch for windowed hosts, RTK fallback otherwise.
130
+ - Admin RPC uses the host-bound native fetch client. Do not add axios.
103
131
 
104
132
  ## 6. Sidebar and grouping
105
133
 
@@ -108,7 +136,8 @@ Do not add sidebar links in app code. The shell reads `/admin/config`.
108
136
 
109
137
  Platform links appear only when `platformTools` says they should. Pass
110
138
  `configurationPath`, `rolesPath`, and `versionConfigPath` into `AdminShellLayout`
111
- so those buttons have destinations.
139
+ so those buttons have destinations. Jobs (`customScreens` name `jobs`) appears
140
+ under Platform, not Screens.
112
141
 
113
142
  ## 7. Verify
114
143
 
@@ -0,0 +1,37 @@
1
+ # Compose charts in a dashboard grid
2
+
3
+ Put each chart in a `Card`, then wrap the cards in `DashboardGrid`.
4
+
5
+ ```tsx
6
+ import type {FC} from "react";
7
+ import {BarChart, Card, DashboardGrid, LineChart} from "@terreno/ui";
8
+
9
+ const POINTS = [
10
+ {label: "Mon", value: 3},
11
+ {label: "Tue", value: 5},
12
+ ];
13
+
14
+ export const OpsDashboard: FC = () => {
15
+ return (
16
+ <DashboardGrid>
17
+ <Card>
18
+ <LineChart data={POINTS} legendLabel="Signups" />
19
+ </Card>
20
+ <Card>
21
+ <BarChart data={POINTS} legendLabel="Sessions" />
22
+ </Card>
23
+ </DashboardGrid>
24
+ );
25
+ };
26
+ ```
27
+
28
+ 1. Pass a single series of `{label, value}` points.
29
+ 2. Use `emptyText` and `loading` on the chart, not a custom overlay.
30
+ 3. Press or hover a mark for `{label}: {value}`. Zero and negative bars stay pressable; bars grow from the zero baseline.
31
+ 4. Keep `DashboardGrid` eager; import charts from `@terreno/ui/LineChart` when cold start matters. `gap` is subtracted from cell width so the breakpoint column count still fits.
32
+
33
+ `DonutChart` ignores `legendLabel` and draws one legend row per slice. Override a slice with `color` on that point.
34
+
35
+ Charts have no width prop: each one fills the container you put it in and redraws when that width changes, so size the `Card` or `Box` around it. `height` is the chart's whole height, tick row and tooltip row included, so `height={140}` fits a 140px slot; the tooltip row is always reserved so hovering never reflows the page. Long x labels truncate inside their band rather than widening the chart.
36
+
37
+ Prove paint with `bun run ui:charts:compare` against `demo/rendered-snapshots/`, not with JSON snapshots. See [Compare rendered chart snapshots](compare-chart-rendered-snapshots.md).