@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,231 @@
1
+ # Announcement Surfaces, Targeting, and Click Tracking
2
+
3
+ **Status:** Implementation complete — Phase 4 verified; Taste in progress
4
+ **Parent IP:** [announcements](announcements.md)
5
+ **Branch:** `cursor/announcements-grow-bf9c`
6
+ **Owner:** Terreno
7
+ **Created:** 2026-09-15
8
+ **Linear:** https://linear.app/flourish-health/project/product-update-announcements-in-app-944f756c05c9/overview
9
+
10
+ v1 (`@terreno/announcements` + blocking `AnnouncementNavigator`) lives on this same PR. This follow-up ships on that branch — do not open a second PR.
11
+
12
+ ## Goal
13
+
14
+ One `Announcement` collection can serve **staff** (blocking modal + required ack) and **patients** (non-blocking banner / feed-only, dismiss-only) without a second plugin. Admins pick display mode, audience type, acknowledgement policy, and optional min build. Clients cap interrupt frequency locally. Primary-action clicks are first-class analytics.
15
+
16
+ **Tracer bullet:** `GET /announcements/pending?platform=&version=` → `AnnouncementNavigator` renders **banner or modal** from `displayMode` → `POST /announcements/:id/click` on primary action.
17
+
18
+ ## Non-Goals
19
+
20
+ - Multi-locale `title` / `body`.
21
+ - Socket push when an announcement publishes.
22
+ - Snooze / remind-later.
23
+ - Per-platform min builds (`{ios, android, web}`).
24
+ - Server-enforced cooldown or cross-device frequency caps.
25
+ - Email/push digest (`@terreno/comms` stays separate).
26
+ - Replacing opaque `audience` JSON — `audienceType` is additive; `matchAudience` still owns custom targeting.
27
+
28
+ ## Decisions
29
+
30
+ | Question | Decision |
31
+ |----------|----------|
32
+ | Q1 — Min app version | Optional `minBuildNumber` (integer, same scale as `VersionCheckPlugin`). Client sends `?version=` on pending/feed/help. Omit/`0` on the announcement = no gate. Omit `version` query = do not hide gated items. |
33
+ | Q2 — Frequency caps | Client-only in `AnnouncementNavigator` (AsyncStorage). Plugin UI props: `maxInterruptionsPerSession` (default `1`), `cooldownHours` (default unset = off), `skipFirstLaunch` (default `false`). Server still returns the full interrupt queue. |
34
+ | Q3 — Acknowledgement | Per-announcement `acknowledgementPolicy: "required" \| "dismiss-only"`. Drop `acknowledgementMode` and `requiresAcknowledgement`. Consumer `defaultAcknowledgementPolicy` on `AnnouncementsApp` fills omitted policy at read time and pre-fills the admin editor. |
35
+ | Q4 — CTA clicks | New `AnnouncementClickEvent` + `POST /announcements/:id/click` with `{action: "primaryAction"}`. Do not overload impressions. |
36
+ | Q5 — Pending queue | `GET /pending` returns only `displayMode: "modal" \| "banner"`. `feed` is changelog-only. Existing rows default `displayMode: "modal"`. |
37
+ | Q6 — Targeting | First-class `audienceType: "staff" \| "patient" \| "all"` (default `"all"`) composed with existing `audience` + `matchAudience`. Package exports `matchAudienceByType`. |
38
+ | Q7 — Collection | Still one Mongoose `Announcement` model / one `AnnouncementsApp`. |
39
+
40
+ ## Architecture
41
+
42
+ ```
43
+ Announcement
44
+ displayMode, audienceType, acknowledgementPolicy, minBuildNumber
45
+ + existing lifecycle / audience / platforms / primaryAction
46
+
47
+ GET /announcements/pending?platform=&version= → interrupt queue only (modal + banner)
48
+ GET /announcements/feed?platform=&version= → changelog (all display modes)
49
+ GET /announcements/config → { defaultAcknowledgementPolicy } (admin)
50
+ POST /announcements/:id/click → AnnouncementClickEvent
51
+ POST /announcements/:id/acknowledge | impression → same visibility as pending/feed (404 if hidden)
52
+
53
+ AnnouncementNavigator
54
+ modal → blocks children (today)
55
+ banner → children stay mounted; AnnouncementBanner overlay (one at a time)
56
+ frequency caps applied client-side after pending fetch
57
+ ```
58
+
59
+ v1 is **not published**. Replace `requiresAcknowledgement` / `acknowledgementMode` in this PR. Read-path fallback if a local doc still has the boolean: `true` → `"required"`, else `defaultAcknowledgementPolicy`.
60
+
61
+ ## Models
62
+
63
+ ### Announcement (additive + replace ack fields)
64
+
65
+ | Field | Type | Default | Notes |
66
+ |-------|------|---------|-------|
67
+ | `displayMode` | `"modal" \| "banner" \| "feed"` | `"modal"` | Pending includes `modal` and `banner` only. |
68
+ | `audienceType` | `"staff" \| "patient" \| "all"` | `"all"` | Admin control + `matchAudienceByType`. Opaque `audience` Mixed stays. |
69
+ | `acknowledgementPolicy` | `"required" \| "dismiss-only"` | unset | Resolve: field ?? `defaultAcknowledgementPolicy` ?? `"dismiss-only"`. |
70
+ | `minBuildNumber` | `number` optional | unset | Hide when query `version` is a finite integer **and** `version < minBuildNumber`. |
71
+
72
+ Remove schema + options field `requiresAcknowledgement` and plugin option `acknowledgementMode`. Public DTO still includes **resolved** `requiresAcknowledgement: boolean` plus `displayMode` so the navigator does not re-implement policy.
73
+
74
+ Every new schema field needs a `description` (OpenAPI). Types in `announcements/src/types.ts` updated in the same commit.
75
+
76
+ ### AnnouncementClickEvent (new)
77
+
78
+ ```typescript
79
+ {
80
+ userId: ObjectId; // ref User, required, indexed
81
+ announcementId: ObjectId; // ref Announcement, required, indexed
82
+ version: number; // announcement version at click
83
+ action: "primaryAction"; // enum — only this value in this slice
84
+ clickedAt: Date;
85
+ platform?: "ios" | "android" | "web";
86
+ }
87
+ ```
88
+
89
+ Plugins: `createdUpdatedPlugin`, `isDeletedPlugin`, `findExactlyOne`, `findOneOrNone`.
90
+ Index: `{ announcementId: 1, clickedAt: -1 }`. Not unique.
91
+
92
+ ## APIs
93
+
94
+ Base path unchanged (`/announcements`). Auth: user routes `IsAuthenticated`; config + click-list `IsAdmin`.
95
+
96
+ | Method | Path | Change |
97
+ |--------|------|--------|
98
+ | GET | `/announcements/pending` | Query `platform`, `version`. Filter: visible now, platform, min build, `matchAudience`, ack/impression pending rules, **`displayMode` in `modal`/`banner`**. Sort unchanged. `{ current, remainingCount }` among interrupts only. |
99
+ | GET | `/announcements/feed` | Same visibility + min build + audience; **all** display modes. |
100
+ | GET | `/announcements/help/*` | Same min-build and audience filters as pending/feed visibility. |
101
+ | GET | `/announcements/config` | Admin. `{ data: { defaultAcknowledgementPolicy } }`. |
102
+ | POST | `/announcements/:id/click` | Body `{ action: "primaryAction", platform? }`. 404 if announcement not visible to user. 400 if no `primaryAction` or bad `action`. |
103
+ | GET | `/announcement-click-events` | Admin list/read via modelRouter (same pattern as impressions). |
104
+
105
+ **Policy resolution** (pending, feed, help, public DTO):
106
+
107
+ ```
108
+ policy = announcement.acknowledgementPolicy
109
+ ?? options.defaultAcknowledgementPolicy
110
+ ?? "dismiss-only"
111
+ requiresAcknowledgement = policy === "required"
112
+ ```
113
+
114
+ Queue advancement: required → acknowledgement row; dismiss-only → impression (unchanged).
115
+
116
+ **`matchAudienceByType`:**
117
+
118
+ ```typescript
119
+ matchAudienceByType({
120
+ announcement,
121
+ isStaff: (user) => boolean,
122
+ }): boolean
123
+ // all → true; staff → isStaff(user); patient → !isStaff(user)
124
+ ```
125
+
126
+ Consumers compose: `matchAudienceByType(...) && customAudienceJson(...)`.
127
+
128
+ ## Notifications
129
+
130
+ None. Still poll/refetch on navigator mount and focus.
131
+
132
+ ## UI
133
+
134
+ ### `@terreno/ui`
135
+
136
+ | Piece | Behavior |
137
+ |-------|----------|
138
+ | `AnnouncementPublic` | Add `displayMode`. Keep resolved `requiresAcknowledgement`. |
139
+ | `useAnnouncements` | Send `platform` and integer `version` (Expo `Constants.expoConfig.extra.buildNumber` / same source as `useUpgradeCheck`). |
140
+ | `AnnouncementNavigator` | If `current.displayMode === "modal"`, block children (v1). If `"banner"`, render children **and** `AnnouncementBanner`. Frequency filter runs on `current` before show. |
141
+ | `AnnouncementBanner` | Compose existing `Banner`. Title, dismiss (or ack when required), optional primary action. Impression on show. |
142
+ | `AnnouncementScreen` | Unchanged modal body; primary action also `POST /click`. |
143
+ | `useAcknowledgeAnnouncement` | Add `recordClick(id)`. |
144
+ | Frequency helper | AsyncStorage keys namespaced by user id when available, else `"anon"`. Session counter in memory. Skip first launch: if no `hasLaunched` key, set it and skip interrupts this launch. Cooldown: last interrupt timestamp. Caps apply to **interrupts only**, not the feed screen. |
145
+
146
+ ```tsx
147
+ <AnnouncementNavigator
148
+ api={terrenoApi}
149
+ frequency={{
150
+ maxInterruptionsPerSession: 1,
151
+ cooldownHours: 24,
152
+ skipFirstLaunch: true,
153
+ }}
154
+ >
155
+ <AppTabs />
156
+ </AnnouncementNavigator>
157
+ ```
158
+
159
+ Example app: leave `skipFirstLaunch: false` so the seeded modal still appears on first run.
160
+
161
+ ### `@terreno/admin-frontend`
162
+
163
+ `AnnouncementEditor` / `AnnouncementList`: structured selects for `displayMode`, `audienceType`, `acknowledgementPolicy` (pre-filled from `/announcements/config`), optional `minBuildNumber`. Keep `audience` JSON as an advanced field. List columns: status, displayMode, audienceType, policy.
164
+
165
+ ## Phases
166
+
167
+ | Phase | Scope |
168
+ |-------|--------|
169
+ | **1 — Backend contracts** | Schema, pending/feed/help filters, config route, click model + POST, tests |
170
+ | **2 — Consumer UI** | Banner navigator, version query, frequency, click mutation |
171
+ | **3 — Admin + example + docs** | Editor fields, example `matchAudienceByType` + seeds, how-to/reference |
172
+ | **4 — Admin launch overview** | Overview API + admin dashboard UI + nav/media wiring |
173
+
174
+ ## Phase 4 — Admin launch overview
175
+
176
+ Launch-quality admin landing for announcements: one overview screen backed by aggregate metrics instead of scanning raw event collections.
177
+
178
+ | Deliverable | Scope |
179
+ |-------------|--------|
180
+ | **4.1 API** | `GET /announcements/overview?page=&limit=` — admin-only; rows with resolved list fields + per-announcement metrics across all event versions; global totals and status counts via aggregation; deterministic sort (`priority` desc, `publishedAt` desc); pagination `{ data, totals, page, limit, total, more }`; OpenAPI for SDK |
181
+ | **4.2 UI** | `@terreno/admin-frontend` overview screen consuming overview hook; summary cards from `totals`; table from `data` with metrics columns |
182
+ | **4.3 Nav/media** | Grouped Announcements sidebar (overview + audit models), example route, platform multiselect, and rich launch seed with YouTube/screenshots |
183
+
184
+ Task checklist: **`docs/tasks/announcements-surfaces.md`** (Phase 4).
185
+
186
+ ## Feature Flags & Migrations
187
+
188
+ - Opt-in remains `AnnouncementsApp` registration.
189
+ - No production backfill: package unreleased. Schema defaults cover new fields. Read-path maps leftover `requiresAcknowledgement` boolean.
190
+ - Click collection is new. Admin `listFields` / `queryFields` updated for new announcement columns.
191
+
192
+ ## Activity Log & User Updates
193
+
194
+ No activity-log integration. Admins list click events like impressions.
195
+
196
+ ## Not Included / Future Work
197
+
198
+ - Localization, sockets, snooze, comms digest.
199
+ - Per-platform `minBuildNumber`.
200
+ - Server-side / cross-device frequency.
201
+ - Additional `action` enum values beyond `primaryAction`.
202
+
203
+ ## Files to Create / Modify
204
+
205
+ | Area | Files |
206
+ |------|--------|
207
+ | Models / types | `announcements/src/models/announcement.ts`, `announcementClickEvent.ts`, `announcements/src/types.ts`, `pending.ts` |
208
+ | Plugin | `announcements/src/announcementsApp.ts`, `help.ts`, tests under `announcements/src/tests/` |
209
+ | UI | `ui/src/AnnouncementBanner.tsx`, `AnnouncementNavigator.tsx`, `AnnouncementScreen.tsx`, `useAnnouncements.ts`, `useAcknowledgeAnnouncement.ts`, `announcementFrequency.ts` (+ tests) |
210
+ | Admin | `admin-frontend/src/AnnouncementEditor.tsx`, `AnnouncementList.tsx` (+ tests) |
211
+ | Example | `example-backend/src/server.ts`, seed scripts, `example-frontend` navigator props |
212
+ | Docs | `docs/reference/announcements.md`, `docs/how-to/product-announcements.md`, this IP, parent IP pointer |
213
+ | Knip | `knip.jsonc` only if a new entry file is not already reachable |
214
+
215
+ ## Task List
216
+
217
+ Executable checklist: **`docs/tasks/announcements-surfaces.md`**.
218
+
219
+ ## Acceptance Criteria
220
+
221
+ - [x] Pending never returns `displayMode: "feed"`. Feed still lists feed-only items.
222
+ - [x] `minBuildNumber` hides the item from pending/feed/help when `version` is present and lower; omitted `version` does not hide.
223
+ - [x] `acknowledgementPolicy` + `defaultAcknowledgementPolicy` resolve to public `requiresAcknowledgement`; `acknowledgementMode` is gone.
224
+ - [x] `audienceType` is stored and honored by `matchAudienceByType`; example-backend uses `user.admin` as staff.
225
+ - [x] Navigator: modal blocks; banner does not; frequency caps skip extra interrupts this session / cooldown / first launch.
226
+ - [x] Primary action records `AnnouncementClickEvent` via `POST /announcements/:id/click`.
227
+ - [x] Admin editor exposes display mode, audience type, policy, min build without requiring raw JSON for those fields.
228
+ - [x] Admin overview shows launch guidance, aggregate totals, and per-announcement impressions, acknowledgements, and CTA clicks.
229
+ - [x] Announcement admin routes share one Announcements sidebar group; the editor uses a platform multiselect.
230
+ - [x] Example staff launch content renders markdown with a YouTube embed, feature screenshots, and tracked CTA.
231
+ - [x] Reference + how-to match shipped behavior in the same slice. New files ≥90% coverage. `bun run announcements:test`, `ui` announcement tests, `admin-frontend` editor tests, `bun run lint`, `bun run compile`, `bun run analyze:full` pass.
@@ -0,0 +1,289 @@
1
+ # Product Update Announcements — In-App Core Feature
2
+
3
+ **Status:** In progress — implementation and Roast complete; pending Brew
4
+ **Branch:** `cursor/announcements-grow-bf9c`
5
+ **Owner:** Terreno
6
+ **Created:** 2026-09-08
7
+ **Linear:** https://linear.app/flourish-health/project/product-update-announcements-in-app-944f756c05c9/overview
8
+
9
+ **Follow-up (same PR):** [Announcement surfaces, targeting, and click tracking](announcements-surfaces.md) — display modes, `audienceType`, acknowledgement policy, client frequency caps, min build, CTA clicks. That IP overrides the v1 non-goals “no role targeting built into Terreno” and “no CTA tracking” for the slice listed there.
10
+
11
+ ## Goal
12
+
13
+ Ship a **core Terreno feature** for admin-managed, in-app product update announcements — similar in scope to feature flags and consent forms. Admins create markdown announcements with scheduling, targeting metadata, and lifecycle controls. Authenticated users see a **priority-ordered modal queue** (one at a time) and can browse a **changelog feed**. Consumers (e.g. Flourish) configure audience matching and acknowledgement policy without forking the package.
14
+
15
+ **Tracer bullet (v1 proof):** `GET /announcements/pending` → `AnnouncementNavigator` → `POST /announcements/:id/acknowledge`.
16
+
17
+ ## Non-Goals
18
+
19
+ - Image upload / Terreno CDN hosting (v1 is **URL-only** markdown for images and video links).
20
+ - Multi-locale content maps (v1 is single `title` + `body`).
21
+ - Real-time socket push when announcements publish (poll/refetch on focus for v1).
22
+ - Snooze / remind-later UX.
23
+ - Role-based targeting built into Terreno (consumers supply `matchAudience` callback).
24
+ - Replacing email/push comms (`@terreno/comms` remains separate).
25
+
26
+ ## Decisions
27
+
28
+ | Question | Decision |
29
+ |----------|----------|
30
+ | Q1 — Package placement | New workspace package **`@terreno/announcements`** (plugin pattern like `@terreno/feature-flags`). |
31
+ | Q2 — Consumer UI | Ship **`AnnouncementNavigator`** + **`AnnouncementScreen`** in **`@terreno/ui`** (mirror `ConsentNavigator`). |
32
+ | Q3 — Targeting | `audience: Mixed` on `Announcement` + consumer **`matchAudience(user, announcement)`** callback. |
33
+ | Q4 — Acknowledgement | **Superseded** by [announcements-surfaces.md](announcements-surfaces.md): per-announcement **`acknowledgementPolicy`** (`required` \| `dismiss-only`) + consumer **`defaultAcknowledgementPolicy`** (default `dismiss-only`). Legacy v1 `requiresAcknowledgement` / `acknowledgementMode` are removed. |
34
+ | Q5 — Lifecycle | **`draft` → `published` → `archived`**; priority queue; **one modal at a time**. |
35
+ | Q6 — Media (defaults) | **URL-only markdown** in v1 — no upload/CDN pipeline. |
36
+ | Q7 — Re-show on edit (defaults) | **Auto `version` bump** when a **published** announcement's `title` or `body` changes; acknowledgements are per `(userId, announcementId, version)`. |
37
+ | Q8 — Localization (defaults) | **Single `title` + `body`** strings in v1 (no locale map). |
38
+ | Q9 — Admin UX | **Custom manager screen** with live markdown preview + publish/archive actions (like `ConsentFormEditor`), not generic CRUD only. |
39
+ | Q10 — v1 extras | **`publishAt`**, **`expiresAt`**, **`platforms`**, **`primaryAction`**, **impression tracking**, **changelog feed** (`GET /announcements/feed`). |
40
+
41
+ ## Architecture
42
+
43
+ ```
44
+ @terreno/announcements (TerrenoPlugin)
45
+ ├─ Announcement model (admin CRUD via modelRouter + adminContribution)
46
+ ├─ AnnouncementAcknowledgement model
47
+ ├─ AnnouncementImpression model
48
+ ├─ GET /announcements/pending → modal queue for current user
49
+ ├─ GET /announcements/feed → published changelog (paginated)
50
+ ├─ POST /announcements/:id/acknowledge
51
+ └─ POST /announcements/:id/impression
52
+
53
+ @terreno/ui
54
+ ├─ AnnouncementNavigator (blocks children while queue non-empty)
55
+ ├─ AnnouncementScreen (markdown body, primary action, acknowledge/dismiss)
56
+ ├─ useAnnouncements / useAcknowledgeAnnouncement (RTK inject pattern)
57
+ └─ MarkdownView enhancement (YouTube + Loom embeds)
58
+
59
+ @terreno/admin-frontend
60
+ ├─ AnnouncementEditor (MarkdownEditor + preview + publish)
61
+ └─ AnnouncementList
62
+
63
+ Consumer app (example-frontend, later Flourish)
64
+ └─ AnnouncementsApp({ isStaff, defaultAcknowledgementPolicy, matchAudience })
65
+ + <AnnouncementNavigator api={terrenoApi} frequency={{ skipFirstLaunch: false, userId }} />
66
+ ```
67
+
68
+ Mirror existing patterns:
69
+
70
+ | Concern | Reference |
71
+ |---------|-----------|
72
+ | Plugin package | `feature-flags/src/featureFlagsApp.ts` |
73
+ | User-blocking navigator | `ui/src/ConsentNavigator.tsx` |
74
+ | Admin custom editor | `admin-frontend/src/ConsentFormEditor.tsx` |
75
+ | Markdown admin widget | `admin-frontend/src/widgets/builtInFieldWidgets.tsx` (`widget: "markdown"`) |
76
+ | RTK hook injection | `rtk/src/useTerrenoFeatureFlags.ts` |
77
+
78
+ ## Models
79
+
80
+ ### Announcement
81
+
82
+ Admin-managed product update definition.
83
+
84
+ ```typescript
85
+ {
86
+ title: string; // required, trim
87
+ body: string; // required, markdown
88
+ status: "draft" | "published" | "archived";
89
+ version: number; // default 1; auto-increment on published title/body edit
90
+ priority: number; // higher = shown first in modal queue; default 0
91
+ acknowledgementPolicy: "required" | "dismiss-only"; // per-announcement; see announcements-surfaces.md
92
+ audience: Mixed; // opaque JSON for consumer matchAudience (roles, segments, etc.)
93
+ publishAt?: Date; // optional scheduled publish (null = immediate when published)
94
+ expiresAt?: Date; // optional; excluded from pending/feed after expiry
95
+ platforms: ("ios" | "android" | "web")[]; // default all three
96
+ primaryAction?: {
97
+ label: string;
98
+ url: string; // deep link or external URL
99
+ };
100
+ publishedAt?: Date; // set when status becomes published
101
+ archivedAt?: Date;
102
+ }
103
+ ```
104
+
105
+ Indexes: `{ status: 1, publishAt: 1 }`, `{ status: 1, priority: -1, publishedAt: -1 }`.
106
+
107
+ Plugins: `createdUpdatedPlugin`, `isDeletedPlugin`, `findExactlyOne`, `findOneOrNone`.
108
+
109
+ **Pre-save hook:** When `status === "published"` and `title` or `body` changes vs previous document, increment `version` (forces re-ack for users who only acknowledged the prior version).
110
+
111
+ ### AnnouncementAcknowledgement
112
+
113
+ ```typescript
114
+ {
115
+ userId: ObjectId; // ref User, required, indexed
116
+ announcementId: ObjectId; // ref Announcement, required, indexed
117
+ version: number; // announcement version at acknowledge time
118
+ acknowledgedAt: Date;
119
+ }
120
+ ```
121
+
122
+ Compound unique index: `{ userId: 1, announcementId: 1, version: 1 }`.
123
+
124
+ ### AnnouncementImpression
125
+
126
+ ```typescript
127
+ {
128
+ userId: ObjectId;
129
+ announcementId: ObjectId;
130
+ version: number;
131
+ viewedAt: Date;
132
+ platform?: "ios" | "android" | "web";
133
+ }
134
+ ```
135
+
136
+ Index: `{ announcementId: 1, viewedAt: -1 }` (admin analytics). Not unique — one row per view event is acceptable for v1.
137
+
138
+ ## APIs
139
+
140
+ Base path configurable (default **`/announcements`**). All routes behind `authenticateMiddleware()`.
141
+
142
+ ### Admin routes (IsAdmin) — via modelRouter + custom actions
143
+
144
+ | Method | Path | Description |
145
+ |--------|------|-------------|
146
+ | GET/POST/PATCH/DELETE | `/announcements` | CRUD on `Announcement` |
147
+ | POST | `/announcements/:id/publish` | `draft` → `published`; sets `publishedAt`; validates `publishAt` ≤ now unless scheduling |
148
+ | POST | `/announcements/:id/archive` | `published` → `archived`; sets `archivedAt` |
149
+ | GET | `/announcement-acknowledgements` | List acks (admin read) |
150
+ | GET | `/announcement-impressions` | List impressions (admin read) |
151
+
152
+ ### User routes (IsAuthenticated)
153
+
154
+ | Method | Path | Description |
155
+ |--------|------|-------------|
156
+ | GET | `/announcements/pending` | Modal queue: published, in window (`publishAt` ≤ now, `expiresAt` null or > now), platform match, `matchAudience` pass, not acknowledged at current `version` (when ack required). Sorted by `priority` desc, then `publishedAt` desc. Returns **at most one** “current” item plus `remainingCount` for UI hint. |
157
+ | GET | `/announcements/feed` | Paginated published changelog (same filters except acknowledgement). Query: `limit`, `page` / cursor. |
158
+ | POST | `/announcements/:id/acknowledge` | Create `AnnouncementAcknowledgement` for current version. Idempotent per version. |
159
+ | POST | `/announcements/:id/impression` | Record view when modal opens or feed item becomes visible. Body optional `{ platform }`. |
160
+
161
+ ### Plugin constructor
162
+
163
+ ```typescript
164
+ interface AnnouncementsOptions {
165
+ basePath?: string; // default "/announcements"
166
+ defaultAcknowledgementPolicy?: "required" | "dismiss-only"; // default "dismiss-only"
167
+ isStaff?: (user: User) => boolean; // default user.admin === true
168
+ matchAudience?: (
169
+ user: User,
170
+ announcement: AnnouncementDocument
171
+ ) => boolean | Promise<boolean>;
172
+ permissions?: Partial<ModelRouterPermissions>; // override IsAdmin defaults
173
+ }
174
+
175
+ class AnnouncementsApp implements TerrenoPlugin {
176
+ constructor(options?: AnnouncementsOptions);
177
+ register(app: express.Application, openApi?: unknown): void;
178
+ adminContribution(): AdminContribution;
179
+ }
180
+ ```
181
+
182
+ **Acknowledgement resolution:** per-announcement `acknowledgementPolicy` with consumer `defaultAcknowledgementPolicy`. Legacy `requiresAcknowledgement: true` maps to `"required"` on read. See [announcements-surfaces.md](announcements-surfaces.md) and `docs/reference/announcements.md`.
183
+
184
+ ## Notifications
185
+
186
+ None in v1. Queue is fetched on app launch / navigator mount and on RTK refetch (window focus). No push/email integration.
187
+
188
+ ## UI
189
+
190
+ ### `@terreno/ui`
191
+
192
+ | Component | Description |
193
+ |-----------|-------------|
194
+ | `AnnouncementNavigator` | Wraps app content. Fetches `GET /announcements/pending`. While queue non-empty, renders `AnnouncementScreen` for current item. On acknowledge/dismiss, advances or refetches. Auth errors pass through to children (same as `ConsentNavigator`). |
195
+ | `AnnouncementScreen` | Title, scrollable `MarkdownView` body, optional `primaryAction` link button, acknowledge button (when required) or dismiss. Records impression on mount. |
196
+ | `useAnnouncements` | RTK-injected query for pending + feed. |
197
+ | `useAcknowledgeAnnouncement` | RTK mutation; invalidates pending cache. |
198
+ | `MarkdownView` (enhancement) | Custom link/image renderer: YouTube and Loom URLs render as embedded players (web: iframe; native: WebView or platform-appropriate embed). Plain image URLs continue to render as images. |
199
+
200
+ ### `@terreno/admin-frontend`
201
+
202
+ | Component | Description |
203
+ |-----------|-------------|
204
+ | `AnnouncementEditor` | Create/edit: title, body (`MarkdownEditor`), priority, `displayMode`, `audienceType`, `acknowledgementPolicy` (pre-filled from config), optional `minBuildNumber`, advanced `audience` JSON, publishAt/expiresAt, platforms multi-select, primaryAction fields. Live preview pane. Actions: Save draft, Publish, Archive. |
205
+ | `AnnouncementList` | Table: title, status, displayMode, audienceType, acknowledgement policy, priority, version, publishedAt, expiresAt. Row → editor. |
206
+
207
+ Register via `AnnouncementsApp.adminContribution()` with custom routes pointing at these screens (same pattern as consent admin overrides).
208
+
209
+ ### Client integration
210
+
211
+ ```typescript
212
+ import {AnnouncementNavigator} from "@terreno/ui";
213
+ import {AnnouncementsApp} from "@terreno/announcements";
214
+
215
+ // server.ts
216
+ new TerrenoApp({ userModel: User })
217
+ .register(new AnnouncementsApp({
218
+ defaultAcknowledgementPolicy: "dismiss-only",
219
+ isStaff: (user) => user.admin === true,
220
+ matchAudience: (user, a) => mySegmentMatcher(user, a.audience),
221
+ }))
222
+ .start();
223
+
224
+ // _layout.tsx (after auth)
225
+ <AnnouncementNavigator api={terrenoApi}>
226
+ <AppTabs />
227
+ </AnnouncementNavigator>
228
+ ```
229
+
230
+ ## Phases
231
+
232
+ | Phase | Scope | Deliverable |
233
+ |-------|--------|-------------|
234
+ | **1 — Package + API** | Models, `AnnouncementsApp`, pending/feed/ack/impression routes, version bump hook, unit tests | `@terreno/announcements` backend complete |
235
+ | **2 — Markdown embeds** | YouTube/Loom embed rules in `MarkdownView` + tests | Rich media in announcement body |
236
+ | **3 — Consumer UI** | `AnnouncementNavigator`, `AnnouncementScreen`, hooks, tests | `@terreno/ui` integration |
237
+ | **4 — Admin UI** | `AnnouncementEditor`, `AnnouncementList`, adminContribution wiring | Admins manage announcements |
238
+ | **5 — Example + docs** | example-backend/frontend, SDK regen, reference/how-to docs | End-to-end demo |
239
+
240
+ Phases 2 and 3 can run in parallel after Phase 1. Phase 4 can start after Phase 1 (admin does not require navigator).
241
+
242
+ ## Feature Flags & Migrations
243
+
244
+ - **Feature flags:** None. Opt-in via `AnnouncementsApp` registration.
245
+ - **Migrations:** New collections only. No changes to existing schemas.
246
+ - **Workspace:** Add `announcements/` to root `package.json` workspaces; publish as `@terreno/announcements`.
247
+
248
+ ## Activity Log & User Updates
249
+
250
+ No Terreno activity-log integration in v1. `AnnouncementAcknowledgement` and `AnnouncementImpression` provide audit/analytics for admins.
251
+
252
+ ## Not Included / Future Work
253
+
254
+ Surfaces, `audienceType`, per-announcement acknowledgement policy, frequency caps, min build, and CTA click tracking are specified in **[announcements-surfaces.md](announcements-surfaces.md)** (same PR). Remaining later:
255
+
256
+ - Terreno media upload / CDN for inline images.
257
+ - Multi-locale `title` / `body` maps.
258
+ - Socket live push when announcements publish.
259
+ - Snooze / remind-later.
260
+ - A/B variants of announcement content.
261
+ - Email digest of changelog.
262
+
263
+ ## Files to Create / Modify
264
+
265
+ | Area | Files |
266
+ |------|--------|
267
+ | New package | `announcements/package.json`, `announcements/src/announcementsApp.ts`, `announcements/src/models/*.ts`, `announcements/src/index.ts`, tests |
268
+ | UI | `ui/src/AnnouncementNavigator.tsx`, `ui/src/AnnouncementScreen.tsx`, `ui/src/useAnnouncements.ts`, `ui/src/MarkdownView.tsx` (embed rules) |
269
+ | Admin | `admin-frontend/src/AnnouncementEditor.tsx`, `admin-frontend/src/AnnouncementList.tsx` |
270
+ | Example | `example-backend/src/server.ts`, `example-frontend/app/_layout.tsx`, SDK regen |
271
+ | Docs | `docs/reference/announcements.md`, `docs/how-to/product-announcements.md` |
272
+ | Root | `package.json` workspaces, root scripts (`announcements:compile`, `announcements:test`) |
273
+
274
+ ## Task List
275
+
276
+ Executable checklist: **`docs/tasks/announcements.md`**.
277
+
278
+ ## Acceptance Criteria
279
+
280
+ - [ ] `@terreno/announcements` registers as `TerrenoPlugin` with `adminContribution()`.
281
+ - [ ] Admin can create draft, publish, and archive announcements via custom editor.
282
+ - [ ] Published `title`/`body` edit bumps `version`; users with old ack see announcement again when ack required.
283
+ - [ ] `GET /announcements/pending` respects `matchAudience`, `platforms`, schedule window, and acknowledgement state.
284
+ - [ ] `AnnouncementNavigator` shows one modal at a time; children render when queue empty.
285
+ - [ ] YouTube and Loom links in markdown render as embeds (web verified).
286
+ - [ ] `GET /announcements/feed` returns paginated changelog for in-app “What’s new”.
287
+ - [ ] Impression and acknowledgement records persist for admin list views.
288
+ - [ ] Example app demonstrates full flow end-to-end.
289
+ - [ ] Reference and how-to docs updated in same slice as implementation.
@@ -105,9 +105,11 @@ All decisions resolved by the maintainer on 2026-08-09:
105
105
  | [billing-stripe](billing-stripe.md) | Draft | [tasks](../tasks/billing-stripe.md) |
106
106
  | [native-module-baseline](native-module-baseline.md) | Draft | [tasks](../tasks/native-module-baseline.md) |
107
107
  | [rbac-permissions](rbac-permissions.md) | Complete (PR #932) | — |
108
+ | [notification-center](notification-center.md) | Approved | [tasks](../tasks/notification-center.md) |
109
+ | [framework-audit-log](framework-audit-log.md) | Approved | [tasks](../tasks/framework-audit-log.md) |
108
110
 
109
111
  Remaining items (`inbound-webhooks`, `invitations-and-seats`,
110
- `notification-center`, `framework-audit-log`, `rate-limiting`, `job-queues`, …) get IPs as
112
+ `rate-limiting`, `job-queues`, …) get IPs as
111
113
  they approach the top of the queue.
112
114
 
113
115
  ## Not Included