@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,147 @@
1
+ # In-app notifications
2
+
3
+ Add an owner-scoped inbox, channel preferences, and optional comms fan-out to a Terreno app.
4
+
5
+ ## 1. Register `NotificationsApp`
6
+
7
+ On your `TerrenoApp`, register the plugin and pass your user model. When you already use
8
+ `@terreno/comms`, pass `getComms` so `notify()` can fan out to mail, SMS, and push.
9
+
10
+ ```typescript
11
+ import {NotificationsApp, TerrenoApp} from "@terreno/api";
12
+ import {CommsApp, getCommsService} from "@terreno/comms";
13
+
14
+ new TerrenoApp({userModel: User})
15
+ .register(
16
+ new NotificationsApp({
17
+ getComms: getCommsService,
18
+ retainDays: 0,
19
+ userModel: User,
20
+ })
21
+ )
22
+ .register(new CommsApp({/* providers */}))
23
+ .start();
24
+ ```
25
+
26
+ `retainDays` defaults to `0` (keep all rows). Set a positive number to tombstone rows older
27
+ than that many days (no Mongo TTL index).
28
+
29
+ ## 2. Wire comms preferences
30
+
31
+ Export `notificationsBeforeSend` from `@terreno/api` and compose it into `CommsApp`
32
+ `beforeSend`. It cancels outbound mail, SMS, or push when the user turned that channel off.
33
+ The `verification` channel is never cancelled.
34
+
35
+ ```typescript
36
+ import {notificationsBeforeSend} from "@terreno/api";
37
+
38
+ new CommsApp({
39
+ beforeSend: async (context) => {
40
+ const pref = await notificationsBeforeSend({
41
+ channel: context.channel,
42
+ userId: context.userId,
43
+ });
44
+ if (pref?.cancel) {
45
+ return {cancel: true};
46
+ }
47
+ return undefined;
48
+ },
49
+ mail: mailProvider,
50
+ });
51
+ ```
52
+
53
+ ## 3. Send notifications from server code
54
+
55
+ Only the service inserts inbox rows (HTTP create is disabled).
56
+
57
+ ```typescript
58
+ import {getNotificationService} from "@terreno/api";
59
+
60
+ await getNotificationService().notify({
61
+ userId: String(user._id),
62
+ title: "Todo completed",
63
+ body: "Your task is done.",
64
+ href: "/todos",
65
+ kind: "todo",
66
+ });
67
+ ```
68
+
69
+ Fan-out order: inbox write (when `inapp` is on), then mail, SMS, and push when each
70
+ preference is on and a destination exists. Missing email, phone, or push tokens skip that
71
+ channel without failing `notify()`. A provider rejection on one channel is logged and
72
+ does not skip later channels.
73
+
74
+ ## 4. Sync collections on the client
75
+
76
+ Regenerate syncdb hooks (not RTK CRUD) for `notifications` and `notification-preferences`:
77
+
78
+ ```bash
79
+ cd example-frontend
80
+ bun run sync-sdk
81
+ ```
82
+
83
+ Add the generated `SYNC_COLLECTIONS` to `createSyncDb`. Use `useQuery` / `useMutate` (or
84
+ generated hooks) for list, mark-read (`readAt`), and dismiss (delete).
85
+
86
+ ## 5. Presentational UI
87
+
88
+ Import the bell, inbox, and right-side drawer from `@terreno/ui`. The components take
89
+ data and callbacks only — wire them to syncdb in your screen:
90
+
91
+ ```typescript
92
+ import {NotificationBell, NotificationInbox, SideDrawer} from "@terreno/ui";
93
+
94
+ <NotificationBell unreadCount={unreadCount} onPress={() => setOpen((value) => !value)} />
95
+ <SideDrawer
96
+ isOpen={open}
97
+ onClose={() => setOpen(false)}
98
+ position="right"
99
+ renderContent={() => (
100
+ <NotificationInbox
101
+ items={items}
102
+ onMarkRead={(item) => patch({id: item.id, data: {readAt: DateTime.now().toISO()}})}
103
+ onMarkUnread={(item) => patch({id: item.id, data: {readAt: null}})}
104
+ onDismiss={(item) => remove({id: item.id})}
105
+ onOpen={(item) => item.href?.startsWith("/") && router.push(item.href)}
106
+ />
107
+ )}
108
+ >
109
+ {screen}
110
+ </SideDrawer>
111
+ ```
112
+
113
+ Customize the bell without reimplementing its tap target or unread accessibility label:
114
+
115
+ ```typescript
116
+ <NotificationBell
117
+ unreadCount={unreadCount}
118
+ onPress={() => setOpen((value) => !value)}
119
+ renderIcon={({testID}) => <BrandBell testID={testID} />}
120
+ renderBadge={({testID, unreadCount}) => (
121
+ <BrandBubble count={unreadCount} testID={testID} />
122
+ )}
123
+ />
124
+ ```
125
+
126
+ Treat dismissed rows as archived, not deleted. Dismiss PATCHes `archivedAt` through
127
+ syncdb so the row stays in the owner snapshot with its payload. The active inbox
128
+ filters `archivedAt` out; the full history page uses the same `useQuery("notifications")`
129
+ and splits on `archivedAt`. Retention (`retainDays`) still tombstones old rows with
130
+ `deleted: true`; those drop out of history after compaction. Map `archivedAt` (or a
131
+ legacy `deleted` tombstone still in the local store) to `NotificationInboxItem.archived`.
132
+ Archived rows remain readable but `NotificationInbox` hides their dismiss and read-state
133
+ actions.
134
+
135
+ See `example-frontend/components/NotificationCenter.tsx` and
136
+ `example-frontend/app/notifications.tsx` for the drawer and complete history page.
137
+ These components are not in the `@terreno/ui` component demo.
138
+
139
+ The example todo router also demonstrates lifecycle-generated activity notifications:
140
+ `postCreate` reports added todos, `postUpdate` reports a false-to-true `completed`
141
+ transition, and `postDelete` reports deletion.
142
+
143
+ ## Reference
144
+
145
+ - API shapes: [In-app notifications](../reference/api.md#in-app-notifications)
146
+ - UI props: [Notification components](../reference/ui.md#notification-components)
147
+ - Comms hook: [beforeSend composition](../reference/comms.md#notification-preferences-hook)
@@ -13,15 +13,20 @@ skills.
13
13
 
14
14
  ## Install as a host plugin
15
15
 
16
- The combined lifecycle and Terreno app workflows ship as one host plugin. Stage names
17
- differ by host because Claude Code takes a plugin skill's command from the frontmatter
18
- `name`.
16
+ Two plugins ship from this marketplace: the combined lifecycle and Terreno app workflows,
17
+ and the optional goal-driven scan plugin. Stage names differ by host because Claude Code
18
+ takes a plugin skill's command from the frontmatter `name`.
19
19
 
20
- | Host | Plugin | Invoke Grow |
21
- | --- | --- | --- |
22
- | Cursor | `terreno-planning` | `/terreno-1-grow` |
23
- | Codex | `terreno-planning` | `$terreno-1-grow` |
24
- | Claude Code | `terreno` | `/terreno:1-grow` |
20
+ | Host | Plugin | Invoke Grow | Scan plugin | Invoke Aim |
21
+ | --- | --- | --- | --- | --- |
22
+ | Cursor | `terreno-planning` | `/terreno-1-grow` | `terreno-scan` | `/terreno-scan-1-aim` |
23
+ | Codex | `terreno-planning` | `$terreno-1-grow` | `terreno-scan` | `$terreno-scan-1-aim` |
24
+ | Claude Code | `terreno` | `/terreno:1-grow` | `terreno-scan` | `/terreno-scan:1-aim` |
25
+
26
+ `terreno-scan` depends on the lifecycle plugin: Plot hands each slice to Grow. Its
27
+ resident loop (`/terreno-scan:loop`, `/terreno-scan-loop`, `$terreno-scan-loop`) keeps a
28
+ goal running and heartbeats over the PRs it opens. See the
29
+ [scan plugin reference](../reference/scan-plugin.md).
25
30
 
26
31
  ### Cursor
27
32
 
@@ -32,6 +37,7 @@ Install `terreno-planning` from [`.cursor-plugin/marketplace.json`](https://gith
32
37
  ```text
33
38
  codex plugin marketplace add FlourishHealth/terreno
34
39
  codex plugin install terreno-planning --source terreno-plugins
40
+ codex plugin install terreno-scan --source terreno-plugins
35
41
  $terreno-1-grow
36
42
  ```
37
43
 
@@ -45,12 +51,15 @@ Codex installs the canonical plugin at
45
51
  ```text
46
52
  /plugin marketplace add FlourishHealth/terreno
47
53
  /plugin install terreno@terreno-plugins
54
+ /plugin install terreno-scan@terreno-plugins
48
55
  /terreno:1-grow
49
56
  ```
50
57
 
51
58
  Marketplace: [`.claude-plugin/marketplace.json`](https://github.com/FlourishHealth/terreno/blob/master/.claude-plugin/marketplace.json).
52
59
  Claude Code stages, app skills, and agents come from the generated copy at
53
- [`plugins/terreno-claude/`](https://github.com/FlourishHealth/terreno/tree/master/plugins/terreno-claude).
60
+ [`plugins/terreno-claude/`](https://github.com/FlourishHealth/terreno/tree/master/plugins/terreno-claude);
61
+ scan stages come from
62
+ [`plugins/terreno-scan-claude/`](https://github.com/FlourishHealth/terreno/tree/master/plugins/terreno-scan-claude).
54
63
 
55
64
  ## What you get
56
65
 
@@ -60,6 +69,7 @@ Claude Code stages, app skills, and agents come from the generated copy at
60
69
  | Terreno apps | backend, UI, admin interfaces, data, schema, SDK, prompts, upgrades, deployment |
61
70
  | Docs | `update-docs`, `update-agent-docs`, architecture skills |
62
71
  | GitHub | issues, review, UI verification, release, dependency updates |
72
+ | Code scans | `terreno-scan-1-aim` … `terreno-scan-5-track`, `terreno-scan-campaign`, `terreno-scan-loop` |
63
73
  | Expo and native | `track-upstream-expo`, `upgrading-expo`, deployment / EAS skills |
64
74
  | Plugin agents | `pre-commit`, `ui-verifier` |
65
75
 
@@ -73,10 +83,12 @@ Canonical sources:
73
83
 
74
84
  1. `plugins/terreno-planning/skills/` — lifecycle and reusable Terreno app workflows
75
85
  2. `plugins/terreno-planning/agents/` — reusable verification agents
76
- 3. `.rulesync/skills/` — repository-only and optional Expo skills (`bun run rules` generates agent copies)
77
- 4. `<package>/.ai/skills/` — package/MCP-specific copies; not installable overlays
86
+ 3. `plugins/terreno-scan/skills/` — scan stages and the campaign loop
87
+ 4. `.rulesync/skills/` — repository-only and optional Expo skills (`bun run rules` generates agent copies)
88
+ 5. `<package>/.ai/skills/` — package/MCP-specific copies; not installable overlays
78
89
 
79
- `plugins/terreno-claude/` is generated from sources 1 and 2 with shortened lifecycle names.
90
+ `plugins/terreno-claude/` is generated from sources 1 and 2, and
91
+ `plugins/terreno-scan-claude/` from source 3, both with shortened skill names.
80
92
  Codex uses the canonical plugin plus committed `.codex-plugin/plugin.json` and
81
93
  `.agents/plugins/marketplace.json` — do not generate a third plugin tree.
82
94
 
@@ -21,7 +21,9 @@ This guide is **AI-context-first**: it is written for an agent (or human) perfor
21
21
  1. **Step A — Auth:** Move login/session to Better Auth (`generateBetterAuthSlice`, `betterAuthAdapter`, `syncDb.start()` on login). Screens can still read/write via RTK Query during this step.
22
22
  2. **Step B — Data:** For each collection, replace RTK Query hooks with syncdb hooks and delete the RTK path for that screen.
23
23
 
24
- The example app completed both steps; todos are syncdb-only while profile/admin still use generated RTK hooks.
24
+ The example app completed both steps. Product Todos and eligible built-in admin
25
+ String-`_id` model CRUD use syncdb. Profile and other non-synced routes keep
26
+ generated RTK hooks; admin RPC uses its host-bound fetch client.
25
27
 
26
28
  **Validation (Task 3.5):** The profile screen (`example-frontend/app/(tabs)/profile.tsx`) was reviewed against this guide. `useGetMeQuery` / `usePatchMeMutation` are **non-synced** custom SDK routes — the guide §8 explicitly says to keep them on RTK. No profile migration is required; the guide is sufficient for that case.
27
29
 
@@ -348,7 +350,7 @@ It runs `@rtk-query/codegen-openapi` against `openapi-config.ts` and writes `sto
348
350
 
349
351
  | Still generated | Stop using for migrated collections |
350
352
  |-----------------|-------------------------------------|
351
- | Auth routes, `GET /auth/me`, admin, AI explorer, version config, feature flags | Collection CRUD — import from `store/syncDbSdk.ts` instead |
353
+ | Auth routes, `GET /auth/me`, AI routes, feature flags, ObjectId admin compatibility CRUD | Collection CRUD — import from `store/syncDbSdk.ts` instead |
352
354
  | Custom REST / RPC endpoints | Todo `realtimeList` / `realtimeDocument` wiring in `store/sdk.ts` |
353
355
 
354
356
  Keep running `bun run sdk` after backend route changes — non-synced screens still import from `store/sdk.ts`.
@@ -371,6 +373,20 @@ cd example-frontend && bun run sync-sdk
371
373
 
372
374
  This writes `store/syncDbSdk.ts` with friendly hooks (`useTodos`, `useCreateTodo`, …) that intentionally differ from RTK names so both SDKs can coexist during migration. Import synced screens from `@/store/syncDbSdk`, not `store/sdk.ts`.
373
375
 
376
+ ### Built-in admin
377
+
378
+ For a synced String-`_id` model, set `sync.adminBroadcast: true` on its existing
379
+ `modelRouter` sync config and inject a dedicated `createSyncDb` client with
380
+ `windowCollections` into `AdminProvider`. REST remains the list membership,
381
+ search, sort, pagination, and bulk-patch surface; TinyBase overlays rows and
382
+ `useMutate` handles create/update/delete. Pass the host's `useConflicts()` result
383
+ as `syncConflicts`.
384
+
385
+ Admin RPC (config, scripts, roles, comms, consent, documents, version config,
386
+ background tasks) uses admin-frontend's host-bound native fetch client. Do not
387
+ add new admin `injectEndpoints`. Terreno 57 keeps `useAdminApi` and the required
388
+ `api` prop only for ObjectId/API-only compatibility; Terreno 58 removes them.
389
+
374
390
  ## 9. Feature flags
375
391
 
376
392
  Post-migration, feature flags stay on `@terreno/rtk` + OpenFeature — **not** on syncdb.
@@ -453,7 +469,7 @@ Before a collection can sync:
453
469
  - [ ] `syncPlugin` on schema (`_syncSeq` stamping)
454
470
  - [ ] `modelRouter("/path", Model, {sync: {scope: ...}})` three-argument form
455
471
  - [ ] Custom scopes include `snapshotFilter`
456
- - [ ] `new SyncApp({getUserScopes?})` for tenant/custom scopes
472
+ - [ ] `new SyncApp({getUserScopes?, accessControl?})` for tenant/custom scopes and admin-window `admin:access` (mount `AdminApp` too so admin `queryFilter` / resource actions apply to hydrate and `|admin` deltas)
457
473
  - [ ] `new RealtimeApp({betterAuth?})` + MongoDB replica set
458
474
  - [ ] No `updateMany`, `deleteMany`, `deleteOne`, `findOneAndDelete`, or `bulkWrite` on synced models
459
475
 
@@ -0,0 +1,155 @@
1
+ # Product announcements
2
+
3
+ Use `@terreno/announcements` on the backend and `AnnouncementNavigator` on the frontend to show admin-managed update surfaces.
4
+
5
+ ## Backend
6
+
7
+ 1. Register the plugin on your `TerrenoApp`:
8
+
9
+ ```typescript
10
+ import {AnnouncementsApp} from "@terreno/announcements";
11
+
12
+ new AnnouncementsApp({
13
+ defaultAcknowledgementPolicy: "dismiss-only",
14
+ // audienceType staff/patient/all is applied inside the plugin via matchAudienceByType.
15
+ isStaff: (user) => (user as {admin?: boolean}).admin === true,
16
+ matchAudience: (user, announcement) => {
17
+ const audience = announcement.audience as {organizationIds?: string[]};
18
+ if (!audience.organizationIds?.length) {
19
+ return true;
20
+ }
21
+ const userOrganizationIds = (user as {organizationIds?: string[]}).organizationIds ?? [];
22
+ return audience.organizationIds.some((organizationId) =>
23
+ userOrganizationIds.includes(organizationId)
24
+ );
25
+ },
26
+ });
27
+ ```
28
+
29
+ Use admin `audienceType` for staff vs patient vs all. Reserve the `audience` JSON field for custom segments (orgs, tiers, feature cohorts) in `matchAudience`. Do **not** call `matchAudienceByType` in your app — the plugin composes it before your callback.
30
+
31
+ When the admin shell uses Terreno RBAC without the legacy `user.admin` flag, pass the same read permission to the overview:
32
+
33
+ ```typescript
34
+ new AnnouncementsApp({
35
+ adminOverviewPermissions: [access.permission({adminAnnouncement: ["read"]})],
36
+ });
37
+ ```
38
+
39
+ For Flourish-style surfaces on one collection:
40
+
41
+ | Audience | Typical `displayMode` | `acknowledgementPolicy` |
42
+ |----------|----------------------|-------------------------|
43
+ | `staff` | `modal` (blocking) | `required` |
44
+ | `patient` | `banner` or `feed` | `dismiss-only` |
45
+ | `all` | any | per announcement or `defaultAcknowledgementPolicy` |
46
+
47
+ 2. Create announcements in admin (draft → publish). Use `AnnouncementOverview`, `AnnouncementEditor`, and (optionally) `AnnouncementList` from `@terreno/admin-frontend` with dedicated Expo routes (see `example-frontend/app/admin/announcements/`). The built-in admin screen widget key `announcements` renders the overview when your host uses `AdminScreenRouter` for custom screens. Published `title`/`body` edits auto-increment `version`, which re-shows the surface to users who only acknowledged the previous version.
48
+
49
+ ### Admin editor fields
50
+
51
+ `AnnouncementEditor` exposes structured targeting controls (no raw `requiresAcknowledgement` boolean):
52
+
53
+ | Field | Control | Notes |
54
+ |-------|---------|-------|
55
+ | `displayMode` | Select | `modal`, `banner`, or `feed` |
56
+ | `audienceType` | Select | `all`, `staff`, or `patient` — composed with `matchAudience` |
57
+ | `acknowledgementPolicy` | Select | `required` or `dismiss-only`. On **create**, pre-filled from `GET /announcements/config` (`defaultAcknowledgementPolicy`); defaults to `dismiss-only` when config is unavailable |
58
+ | `minBuildNumber` | Number (optional) | Minimum client build; cleared with `null` on edit when empty |
59
+ | `platforms` | Multiselect | One or more of iOS, Android, and web; at least one is required |
60
+ | `audience` | JSON textarea (advanced) | Opaque metadata for `matchAudience`; use audience type for staff/patient/all |
61
+
62
+ ### Admin overview
63
+
64
+ `AnnouncementOverview` calls `GET /announcements/overview` (via RTK `injectEndpoints`) and shows:
65
+
66
+ - Launch workflow guidance (draft → targeting → preview → publish) with a primary **Create announcement** action
67
+ - Summary cards for published, drafts, impressions, acknowledgements, and CTA clicks
68
+ - A paginated table with per-row metrics and an edit action
69
+ - Optional quick links to the raw acknowledgement, impression, and click-event admin tables
70
+
71
+ Register the screen through `AnnouncementsApp.adminContribution()` (`customScreens: [{ name: "announcements", displayName: "Overview", group: "Announcements" }]`) and rely on `ANNOUNCEMENTS_ADMIN_WIDGETS` from `@terreno/admin-frontend` (merged into `BUILT_IN_SCREEN_WIDGETS`). Grouped custom screens appear in the **Announcements** sidebar group before model links; ungrouped screens stay under **Screens**.
72
+
73
+ `AnnouncementList` remains available for a CRUD-style list; the overview is the launch dashboard.
74
+
75
+ `AnnouncementEditor` uses `MultiselectField` for platforms and requires at least one platform before save.
76
+
77
+ <img alt="Announcement overview with delivery metrics" src="/img/announcements/admin-overview.png" />
78
+
79
+ <img alt="Platform multiselect in the announcement editor" src="/img/announcements/admin-editor-platforms.png" />
80
+
81
+ ## Frontend
82
+
83
+ Wrap authenticated app content:
84
+
85
+ ```tsx
86
+ import {AnnouncementNavigator} from "@terreno/ui";
87
+
88
+ <AnnouncementNavigator api={terrenoApi}>
89
+ <AppTabs />
90
+ </AnnouncementNavigator>
91
+ ```
92
+
93
+ Place it after consent/onboarding wrappers if you use `ConsentNavigator`. The navigator sends the native platform (`ios` / `android` / `web`) and, when available, the app build number from `Constants.expoConfig.extra.buildNumber` (same source as `useUpgradeCheck`) on pending and feed requests. Only pending failures block the app — changelog feed errors are non-fatal. These surfaces are not in the `@terreno/ui` component demo; wire them in the app (`example-frontend`) the same way.
94
+
95
+ ## Modal vs banner
96
+
97
+ | `displayMode` | Navigator behavior |
98
+ |---------------|-------------------|
99
+ | `modal` | Renders `AnnouncementScreen` and hides children until the announcement is cleared. |
100
+ | `banner` | Keeps children mounted and renders `AnnouncementBanner` above them (one interrupt at a time). |
101
+ | `feed` | Changelog-only; never shown by `AnnouncementNavigator` even if it appears unexpectedly in `pending.current`. |
102
+
103
+ `AnnouncementBanner` composes the existing `Banner` component: title text, a dismiss or acknowledgement action, and an optional primary-action button. When used inside `AnnouncementNavigator`, primary CTA presses call `POST /announcements/:id/click` (via `useAcknowledgeAnnouncement().recordClick`) and then open the URL; tracking failures never block navigation.
104
+
105
+ Acknowledgement policy still resolves to `requiresAcknowledgement` on the public DTO:
106
+
107
+ | Policy | Banner / modal behavior |
108
+ |--------|-------------------------|
109
+ | `required` | Primary action is **Got it**, which acknowledges through the existing handler. |
110
+ | `dismiss-only` | Dismiss records an impression only. |
111
+
112
+ Impressions are recorded once per announcement version while the modal or banner is visible.
113
+
114
+ ## Client frequency caps
115
+
116
+ Interrupt frequency is enforced **client-side** in `AnnouncementNavigator` before a modal or banner is shown. The server still returns the full pending queue; caps never filter `useAnnouncements` feed data.
117
+
118
+ ```tsx
119
+ <AnnouncementNavigator
120
+ api={terrenoApi}
121
+ frequency={{
122
+ cooldownHours: 24,
123
+ skipFirstLaunch: false,
124
+ userId: currentUser?.id,
125
+ }}
126
+ >
127
+ <AppTabs />
128
+ </AnnouncementNavigator>
129
+ ```
130
+
131
+ `maxInterruptionsPerSession` defaults to `1` when omitted. The example app sets `skipFirstLaunch: false` so seeded interrupts still appear on first launch.
132
+
133
+ | Prop | Default | Behavior |
134
+ |------|---------|----------|
135
+ | `maxInterruptionsPerSession` | `1` | In-memory module session counter. After the cap is reached, pending interrupts are skipped until the JS runtime reloads (cold start / full app restart). Remounting `AnnouncementNavigator` alone does **not** reset the counter. |
136
+ | `cooldownHours` | off | When set, skips interrupts if the last shown interrupt was within this many hours. Timestamp is persisted in AsyncStorage when an interrupt becomes visible. |
137
+ | `skipFirstLaunch` | `false` | When `true`, the first app launch ever (no `hasLaunched` key) records the flag and skips interrupts for that launch only. |
138
+ | `userId` | `"anon"` | AsyncStorage namespace for frequency keys when a signed-in user id is available. |
139
+
140
+ When a cap applies, the navigator renders children (does not block the app) and does **not** record an impression. AsyncStorage read/write failures fail open with a `console.warn` and allow the interrupt to show. If `hasLaunched` cannot be persisted on first launch, the interrupt is shown and the in-memory first-launch skip is not armed for the rest of the session.
141
+
142
+ Tests call `resetFrequencySessionStateForTests()` to simulate a cold start; that helper is not part of the production API.
143
+
144
+ ## Media in markdown
145
+
146
+ Paste YouTube or Loom URLs in the announcement `body` using markdown links or images, for example `[Watch the demo](https://www.youtube.com/watch?v=...)`. `MarkdownView` renders them as embeds (iframe on web, WebView on native). Banner surfaces show the title only; use modal mode or the feed when the full body should be visible.
147
+
148
+ ## Example app
149
+
150
+ `example-backend` registers `AnnouncementsApp` with `isStaff: (user) => user.admin === true` and an optional `matchAudience` org filter. `bun run backend:seed` idempotently seeds:
151
+
152
+ - **Staff modal (required)** — `audienceType: "staff"`, `displayMode: "modal"`, `acknowledgementPolicy: "required"` (`Example staff operations bulletin`)
153
+ - **Patient banner (dismiss-only)** — `audienceType: "patient"`, `displayMode: "banner"`, `acknowledgementPolicy: "dismiss-only"` (`Example patient care tip`)
154
+
155
+ The staff launch example is rich markdown with a YouTube embed, overview/editor screenshots, and a tracked docs CTA. Re-running the seed updates that example in place. An existing legacy welcome row (`Welcome to Terreno announcements`) is archived idempotently so it no longer blocks the patient banner. `example-frontend` wraps authenticated users with `AnnouncementNavigator` (`skipFirstLaunch: false`, `userId` from the profile); non-staff users therefore see only the patient banner interrupt under the default session cap of `1`.
@@ -0,0 +1,127 @@
1
+ # Run MongoDB migrations
2
+
3
+ Use `terreno-migrate` for schema and data changes that must run once per environment. Keep ad-hoc admin scripts for one-off maintenance.
4
+
5
+ ## Add a file
6
+
7
+ Put TypeScript modules in `migrations/`:
8
+
9
+ ```text
10
+ migrations/20260910120000-add-todos-index.ts
11
+ ```
12
+
13
+ The exported `id` must match the filename stem. `up` is required; `down` is optional.
14
+
15
+ ```typescript
16
+ export const id = "20260910120000-add-todos-index";
17
+
18
+ export const up = async ({dryRun, mongoose}): Promise<void> => {
19
+ if (dryRun) {
20
+ return;
21
+ }
22
+ await mongoose.connection.collection("todos").createIndex({owner: 1});
23
+ };
24
+
25
+ export const down = async ({dryRun, mongoose}): Promise<void> => {
26
+ if (dryRun) {
27
+ return;
28
+ }
29
+ await mongoose.connection.collection("todos").dropIndex("owner_1");
30
+ };
31
+ ```
32
+
33
+ ## First apply
34
+
35
+ 1. Generate a draft from the current models vs the last `schemaAfter` snapshot:
36
+ `terreno-migrate generate --dir ./migrations --models ./src/models.ts`
37
+ 2. Dry-run pending files (no history write):
38
+ `terreno-migrate up --dir ./migrations --dry`
39
+ 3. Apply for real:
40
+ `terreno-migrate up --dir ./migrations`
41
+ 4. In production, set `ALLOW_MIGRATIONS=true` and pass `--force` on that last command.
42
+
43
+ If generate prints `No schema changes`, there is nothing to apply. Replace any fail-closed stub in the new file before step 2.
44
+
45
+ ## Commands
46
+
47
+ `terreno-migrate` is the `@terreno/api` bin. `up`, `down`, and `status` need `MONGO_URI` or `MONGODB_URI`.
48
+
49
+ | Command | Mongo | Effect |
50
+ |---------|-------|--------|
51
+ | `terreno-migrate check --dir ./migrations` | No | Load and validate files (CI) |
52
+ | `terreno-migrate generate --dir ./migrations --models ./src/models.ts [--name slug]` | No | Diff current models vs last `schemaAfter`; write one file or print `No schema changes` |
53
+ | `terreno-migrate status --dir ./migrations` | Yes | List applied and pending ids |
54
+ | `terreno-migrate up --dir ./migrations [--dry] [--force]` | Yes | Apply pending `up` in filename order |
55
+ | `terreno-migrate down --dir ./migrations [--dry] [--force] [--steps N]` | Yes | Roll back the last `N` applied files (default 1) |
56
+
57
+ `--dry` calls `up`/`down` with `dryRun: true` and does **not** write `terreno_migrations` history. Authors must no-op writes when `dryRun` is true.
58
+
59
+ `down` fails with `Migration has no down` when the target file omits `down`. History stays unchanged.
60
+
61
+ ## What generate can emit
62
+
63
+ `buildSchemaCatalog` / `diffSchemaCatalog` classify model changes. `terreno-migrate generate` writes one file per non-empty diff:
64
+
65
+ | Diff | Safe? | Generated `up` |
66
+ |------|-------|----------------|
67
+ | Add optional field | Yes | Snapshot only (Mongo is schemaless); comment, no write |
68
+ | Add / drop non-unique index | Yes | `createIndex` / `dropIndex`, skipped when `dryRun` |
69
+ | Add required field (no default) | No | Fail-closed stub that throws in dry-run and wet until you write a backfill |
70
+ | Unique index | No | Fail-closed stub (valid JavaScript `throw`) |
71
+ | Rename (removed + added field, same type) | No | Fail-closed stub |
72
+ | Remove field | No | Fail-closed stub |
73
+ | Same-path type change | No | Fail-closed stub |
74
+
75
+ A file with any unsafe op is still a throw stub (dry-run and wet). Safe `createIndex` /
76
+ `dropIndex` calls are kept as comments in that stub and listed in the throw message.
77
+
78
+ Do not inspect the live database during generate. Baseline is the last `schemaAfter` snapshot in `migrations/`.
79
+
80
+ ## Admin HTTP
81
+
82
+ Pass `migrations: {dir: "./migrations"}` into `AdminApp`. Both routes are
83
+ `modelRouter` collection actions under the `adminMigrations` OpenAPI tag, so they appear in
84
+ `/openapi.json` and in generated SDKs. Then:
85
+
86
+ | Method | Path | Gate | Effect |
87
+ |--------|------|------|--------|
88
+ | GET | `/admin/migrations/status` | `admin:access` (or `IsAdmin`) | Applied, pending, lock (`{data: ...}`) |
89
+ | POST | `/admin/migrations/run?wetRun=true\|false` | `admin:runScripts` (or `IsAdmin`) | `{data: {taskId}}` — one BackgroundTask (`migrations:up`) |
90
+ | GET/DELETE | `/admin/scripts/tasks/:id` | `viewBackgroundTasks` / `runScripts` | Poll or cancel that task |
91
+
92
+ Dry-run and Apply use the same `admin:runScripts` gate as Scripts (`platformTools.runScripts`
93
+ disables the buttons when the caller cannot run scripts). Status listing stays on
94
+ `admin:access`. Collection-action permission denials are **405** (modelRouter). Admin wet
95
+ in production still needs `ALLOW_MIGRATIONS=true` (**403**). The admin UI is
96
+ **Migrations** under Platform (`/admin/__migrations` or `/console/__migrations`):
97
+ status list plus Dry run / Apply pending. Task logs poll `GET /admin/scripts/tasks/:id`.
98
+
99
+ ## Example app
100
+
101
+ `example-backend` keeps files in `migrations/` and wires `resolveExampleMigrationsDir()` into
102
+ `TerrenoApp` and `AdminApp`. From `example-backend/`:
103
+
104
+ ```bash
105
+ bun run migrate:check
106
+ bun run migrate:status
107
+ bun run migrate:up
108
+ ```
109
+
110
+ Compiled deploys (`bun build --compile`, Cloud Run) cannot scandir `$bunfs`. The example
111
+ Dockerfile copies `migrations/` to `/app/migrations` and sets `MIGRATIONS_DIR`. GET
112
+ `/admin/migrations/status` returns **404** `Migration directory not found` when that path is
113
+ missing, not a generic 500.
114
+
115
+
116
+ ## Production wet apply
117
+
118
+ `NODE_ENV=production` wet `up`/`down` requires both:
119
+
120
+ 1. `ALLOW_MIGRATIONS=true`
121
+ 2. `--force`
122
+
123
+ Dry-run is allowed in production without those. Non-production wet apply does not need `--force`.
124
+
125
+ ## History and lock
126
+
127
+ Applied ids live in Mongo collection `terreno_migrations` (lock row `_id: "_lock"`). Do not wipe that collection in seed `--reset`. See [Seed a database](seed-a-database.md).
@@ -0,0 +1,102 @@
1
+ # Run Terreno tests locally
2
+
3
+ Every CircleCI test job has a local command. On Cursor Cloud, run the failed tests
4
+ from the last CI run with these commands before pushing.
5
+
6
+ Package unit tests use in-memory Mongo from `@terreno/test`. Playwright, Maestro, and
7
+ the example-backend **dev** server need a replica-set `mongod` (see
8
+ [AGENTS.md](https://github.com/FlourishHealth/terreno/blob/master/AGENTS.md) Cursor Cloud
9
+ section).
10
+
11
+ ## Do now — package tests
12
+
13
+ From the repo root after `bun bootstrap`:
14
+
15
+ ```bash
16
+ bun run test # every workspace `test:ci`
17
+ bun run test:agent # same suites; passing cases suppressed
18
+ ```
19
+
20
+ Closest package during a red/green cycle:
21
+
22
+ ```bash
23
+ bun test --only-failures <path>
24
+ ```
25
+
26
+ | CircleCI job | Local command |
27
+ | --- | --- |
28
+ | `api-ci` | `bun run api:test` |
29
+ | `ai-ci` | `cd ai && bun run test:ci` |
30
+ | `ui-ci` | `bun run ui:test` |
31
+ | `rtk-ci` | `bun run rtk:test` |
32
+ | `syncdb-ci` | `bun run syncdb:test` |
33
+ | `comms-ci` | `bun run comms:test` |
34
+ | `jobs-ci` / jobs package | `bun run jobs:test` |
35
+ | `mcp-server-ci` | `cd mcp-server && bun run test:ci` |
36
+ | `create-terreno-app-ci` | `cd create-terreno-app && bun run test:ci` |
37
+ | `packages-ci` (`admin-backend`, `admin-frontend`, `api-health`, `feature-flags`, `test`) | `cd <package> && bun run test:ci` |
38
+ | `example-backend-ci` | `cd example-backend && bun run test:ci` |
39
+ | `example-frontend-ci` | `bun run frontend:test` |
40
+ | `ui-demo-ci` | `cd demo && bun run test:ci` then `bun run check:demo-coverage` |
41
+ | `new-file-coverage` | `bun run check:new-file-coverage --base=origin/master --threshold=90` |
42
+ | `repo-policies` | `bun run prepush` (lint, compile, `analyze:full`) |
43
+
44
+ Coverage gate for a published package: `cd <package> && bun run test:coverage`.
45
+
46
+ ## Playwright (example-frontend e2e)
47
+
48
+ 1. Install Chromium once: `cd example-frontend && bunx playwright install --with-deps chromium`
49
+ 2. Start a replica-set `mongod` on `127.0.0.1:27017` (Cursor Cloud steps in AGENTS.md).
50
+ 3. Re-run one failed spec (Playwright starts backend + web if they are not up):
51
+
52
+ ```bash
53
+ cd example-frontend
54
+ MONGO_URI="mongodb://127.0.0.1:27017/terreno-e2e?replicaSet=rs0" \
55
+ bunx playwright test e2e/<spec>.spec.ts --reporter=list
56
+ ```
57
+
58
+ | CircleCI shard | Specs |
59
+ | --- | --- |
60
+ | `e2e` `auth` | `login` `signup` `consents` `forgot-password` `reset-password` `verify-email` |
61
+ | `e2e` `app` | `todos` `profile` `realtime` `ai-chat` `pdf` |
62
+ | `e2e` `admin-core` | `admin` `admin-home` `admin-form` `admin-todo-crud` `admin-title-update-depth` |
63
+ | `e2e` `admin-table` | `admin-table-search-filter` `admin-table-bulk-actions` `admin-custom-screens` `admin-comms-back` |
64
+ | `e2e` `syncdb` | `syncdb-load-delta` `syncdb-offline` `syncdb-conflicts` `syncdb-storage` `syncdb-chaos` |
65
+
66
+ Full local suite: `bun run frontend:e2e`. Nightly load (`e2e-load` / `syncdb-loadlab`) is
67
+ not PR-blocking; run `bunx playwright test e2e/syncdb-loadlab.spec.ts` only when that job
68
+ failed.
69
+
70
+ CircleCI skips a shard no changed file can reach. Ask for the same verdict before
71
+ pushing with `bun run check:e2e-affected` (see
72
+ [the affected gate](circleci.md#e2e-affected-gate)).
73
+
74
+ ## Admin SPA
75
+
76
+ ```bash
77
+ cd admin-spa
78
+ bun run test:ci
79
+ bunx playwright install --with-deps chromium
80
+ bun run test:e2e
81
+ bun run test:integration # needs Mongo + example-backend; job `admin-spa-integration`
82
+ ```
83
+
84
+ ## Maestro web e2e
85
+
86
+ Install CLI if missing: `curl -fsSL "https://get.maestro.mobile.dev" | bash`, then put
87
+ `$HOME/.maestro/bin` on `PATH`.
88
+
89
+ ```bash
90
+ bun run maestro:test
91
+ ```
92
+
93
+ Demo-only flows: `bun run demo:maestro:test`. Job `maestro-e2e` uses a static web export
94
+ plus replica-set Mongo; local Maestro talks to the running example-frontend on 8082.
95
+
96
+ ## Taste mapping
97
+
98
+ When CircleCI names a failed test, copy the file/case from the log and run the matching
99
+ row above. Record that command in Taste `checks.ev`. Re-run it after the fix and before
100
+ `git push`. `bun run prepush` does not replace that re-verify.
101
+
102
+ Appium Android/iOS is not in CircleCI (Maestro web covers those flows).