@terreno/mcp 57.3.0 → 57.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/bootstrap.d.ts +0 -18
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +10 -2428
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
- package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
- package/dist/docs/resources/syncdb.md +4 -1
- package/dist/docs/upgrades/57.4.0.md +140 -0
- package/dist/docs/versioned/next/README.md +3 -0
- package/dist/docs/versioned/next/explanation/README.md +4 -1
- package/dist/docs/versioned/next/explanation/admin-interface.md +20 -13
- package/dist/docs/versioned/next/explanation/charts.md +29 -0
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/dist/docs/versioned/next/explanation/example-coverage.md +43 -0
- package/dist/docs/versioned/next/explanation/local-first-data.md +5 -2
- package/dist/docs/versioned/next/explanation/loop-engineering.md +9 -6
- package/dist/docs/versioned/next/explanation/model-router-actions.md +19 -2
- package/dist/docs/versioned/next/explanation/organizations.md +88 -0
- package/dist/docs/versioned/next/explanation/planning-plugins.md +114 -0
- package/dist/docs/versioned/next/explanation/positioning.md +3 -3
- package/dist/docs/versioned/next/explanation/repository-settings.md +6 -5
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
- package/dist/docs/versioned/next/explanation/source-rules.md +1 -1
- package/dist/docs/versioned/next/explanation/static-analysis.md +76 -16
- package/dist/docs/versioned/next/how-to/README.md +10 -0
- package/dist/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
- package/dist/docs/versioned/next/how-to/add-organizations.md +204 -0
- package/dist/docs/versioned/next/how-to/admin-add-model.md +9 -1
- package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
- package/dist/docs/versioned/next/how-to/audit-log.md +85 -0
- package/dist/docs/versioned/next/how-to/background-jobs.md +296 -0
- package/dist/docs/versioned/next/how-to/build-admin-screens.md +42 -13
- package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
- package/dist/docs/versioned/next/how-to/circleci.md +136 -27
- package/dist/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +4 -0
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
- package/dist/docs/versioned/next/how-to/github-actions-ci.md +4 -3
- package/dist/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
- package/dist/docs/versioned/next/how-to/in-app-notifications.md +147 -0
- package/dist/docs/versioned/next/how-to/install-agent-skills.md +24 -12
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
- package/dist/docs/versioned/next/how-to/product-announcements.md +155 -0
- package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +102 -0
- package/dist/docs/versioned/next/how-to/seed-a-database.md +7 -2
- package/dist/docs/versioned/next/implementationPlans/README.md +8 -5
- package/dist/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
- package/dist/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
- package/dist/docs/versioned/next/implementationPlans/announcements.md +289 -0
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
- package/dist/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
- package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
- package/dist/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
- package/dist/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
- package/dist/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
- package/dist/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
- package/dist/docs/versioned/next/implementationPlans/job-queues.md +330 -0
- package/dist/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
- package/dist/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
- package/dist/docs/versioned/next/implementationPlans/notification-center.md +185 -0
- package/dist/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/dist/docs/versioned/next/reference/README.md +4 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +83 -5
- package/dist/docs/versioned/next/reference/admin-config.md +7 -0
- package/dist/docs/versioned/next/reference/admin-frontend.md +178 -5
- package/dist/docs/versioned/next/reference/admin-spa.md +18 -1
- package/dist/docs/versioned/next/reference/ai.md +2 -0
- package/dist/docs/versioned/next/reference/announcements.md +179 -0
- package/dist/docs/versioned/next/reference/api.md +343 -4
- package/dist/docs/versioned/next/reference/comms.md +33 -0
- package/dist/docs/versioned/next/reference/create-terreno-app.md +44 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +29 -0
- package/dist/docs/versioned/next/reference/jobs.md +319 -0
- package/dist/docs/versioned/next/reference/legacy/rtk.md +5 -1
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
- package/dist/docs/versioned/next/reference/mcp-server.md +2 -43
- package/dist/docs/versioned/next/reference/scan-plugin.md +169 -0
- package/dist/docs/versioned/next/reference/syncdb.md +38 -12
- package/dist/docs/versioned/next/reference/test.md +57 -6
- package/dist/docs/versioned/next/reference/ui-performance.md +2 -1
- package/dist/docs/versioned/next/reference/ui.md +257 -3
- package/dist/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
- package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
- package/dist/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
- package/dist/docs/versioned/next/tasks/announcements.md +173 -0
- package/dist/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
- package/dist/docs/versioned/next/tasks/create-terreno-app.md +82 -0
- package/dist/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
- package/dist/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
- package/dist/docs/versioned/next/tasks/framework-audit-log.md +86 -0
- package/dist/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
- package/dist/docs/versioned/next/tasks/job-queues.md +144 -0
- package/dist/docs/versioned/next/tasks/knip-cleanup.md +146 -0
- package/dist/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
- package/dist/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
- package/dist/docs/versioned/next/tasks/notification-center.md +102 -0
- package/dist/docs/versioned/next/tasks/org-management-ui.md +14 -14
- package/dist/docs/versioned/next/tutorials/README.md +1 -1
- package/dist/docs/versioned/next/tutorials/getting-started.md +62 -22
- package/dist/help/excerpt.d.ts +2 -0
- package/dist/help/excerpt.d.ts.map +1 -0
- package/dist/help/excerpt.js +8 -0
- package/dist/help/excerpt.js.map +1 -0
- package/dist/help/updateNotes.d.ts +42 -0
- package/dist/help/updateNotes.d.ts.map +1 -0
- package/dist/help/updateNotes.js +235 -0
- package/dist/help/updateNotes.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/local/tools/databaseQuery.d.ts +1 -0
- package/dist/local/tools/databaseQuery.d.ts.map +1 -1
- package/dist/local/tools/databaseQuery.js +1 -0
- package/dist/local/tools/databaseQuery.js.map +1 -1
- package/dist/packageGuidelines.d.ts +2 -2
- package/dist/packageGuidelines.d.ts.map +1 -1
- package/dist/packageGuidelines.js +2 -8
- package/dist/packageGuidelines.js.map +1 -1
- package/dist/resources.d.ts +3 -0
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +3 -0
- package/dist/resources.js.map +1 -1
- package/dist/search/chunker.d.ts +1 -0
- package/dist/search/chunker.d.ts.map +1 -1
- package/dist/search/chunker.js +1 -0
- package/dist/search/chunker.js.map +1 -1
- package/dist/search/docIndex.d.ts +1 -5
- package/dist/search/docIndex.d.ts.map +1 -1
- package/dist/search/docIndex.js +1 -0
- package/dist/search/docIndex.js.map +1 -1
- package/dist/search/docVersion.d.ts +2 -1
- package/dist/search/docVersion.d.ts.map +1 -1
- package/dist/search/docVersion.js +3 -1
- package/dist/search/docVersion.js.map +1 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +113 -2
- package/dist/tools.js.map +1 -1
- package/package.json +28 -27
- package/src/docs/guidelines/admin-frontend/guidelines/core.md +5 -1
- package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +23 -10
- package/src/docs/resources/syncdb.md +4 -1
- package/src/docs/upgrades/57.4.0.md +140 -0
- package/src/docs/versioned/next/README.md +3 -0
- package/src/docs/versioned/next/explanation/README.md +4 -1
- package/src/docs/versioned/next/explanation/admin-interface.md +20 -13
- package/src/docs/versioned/next/explanation/charts.md +29 -0
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +32 -0
- package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/src/docs/versioned/next/explanation/example-coverage.md +43 -0
- package/src/docs/versioned/next/explanation/local-first-data.md +5 -2
- package/src/docs/versioned/next/explanation/loop-engineering.md +9 -6
- package/src/docs/versioned/next/explanation/model-router-actions.md +19 -2
- package/src/docs/versioned/next/explanation/organizations.md +88 -0
- package/src/docs/versioned/next/explanation/planning-plugins.md +114 -0
- package/src/docs/versioned/next/explanation/positioning.md +3 -3
- package/src/docs/versioned/next/explanation/repository-settings.md +6 -5
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +44 -34
- package/src/docs/versioned/next/explanation/source-rules.md +1 -1
- package/src/docs/versioned/next/explanation/static-analysis.md +76 -16
- package/src/docs/versioned/next/how-to/README.md +10 -0
- package/src/docs/versioned/next/how-to/add-gpt-chat-mascot.md +92 -0
- package/src/docs/versioned/next/how-to/add-organizations.md +204 -0
- package/src/docs/versioned/next/how-to/admin-add-model.md +9 -1
- package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +7 -1
- package/src/docs/versioned/next/how-to/audit-log.md +85 -0
- package/src/docs/versioned/next/how-to/background-jobs.md +296 -0
- package/src/docs/versioned/next/how-to/build-admin-screens.md +42 -13
- package/src/docs/versioned/next/how-to/charts-and-dashboards.md +37 -0
- package/src/docs/versioned/next/how-to/circleci.md +136 -27
- package/src/docs/versioned/next/how-to/compare-chart-rendered-snapshots.md +36 -0
- package/src/docs/versioned/next/how-to/configure-better-auth.md +4 -0
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +154 -0
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +2 -0
- package/src/docs/versioned/next/how-to/github-actions-ci.md +4 -3
- package/src/docs/versioned/next/how-to/github-issue-lifecycle.md +12 -8
- package/src/docs/versioned/next/how-to/in-app-notifications.md +147 -0
- package/src/docs/versioned/next/how-to/install-agent-skills.md +24 -12
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +19 -3
- package/src/docs/versioned/next/how-to/product-announcements.md +155 -0
- package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +127 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +102 -0
- package/src/docs/versioned/next/how-to/seed-a-database.md +7 -2
- package/src/docs/versioned/next/implementationPlans/README.md +8 -5
- package/src/docs/versioned/next/implementationPlans/admin-scripts-durable-jobs.md +44 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +386 -0
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +11 -8
- package/src/docs/versioned/next/implementationPlans/announcements-surfaces.md +231 -0
- package/src/docs/versioned/next/implementationPlans/announcements.md +289 -0
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +3 -1
- package/src/docs/versioned/next/implementationPlans/charts-and-dashboards.md +246 -0
- package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +4 -3
- package/src/docs/versioned/next/implementationPlans/create-terreno-app.md +177 -0
- package/src/docs/versioned/next/implementationPlans/datatable-server-side-filtering.md +219 -0
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +17 -17
- package/src/docs/versioned/next/implementationPlans/framework-audit-log.md +247 -0
- package/src/docs/versioned/next/implementationPlans/gpt-chat-mascot.md +40 -0
- package/src/docs/versioned/next/implementationPlans/job-queues.md +330 -0
- package/src/docs/versioned/next/implementationPlans/knip-cleanup.md +136 -0
- package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +210 -0
- package/src/docs/versioned/next/implementationPlans/mongodb-migrations-tooling.md +254 -0
- package/src/docs/versioned/next/implementationPlans/notification-center.md +185 -0
- package/src/docs/versioned/next/implementationPlans/org-management-ui.md +8 -3
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
- package/src/docs/versioned/next/reference/README.md +4 -0
- package/src/docs/versioned/next/reference/admin-backend.md +83 -5
- package/src/docs/versioned/next/reference/admin-config.md +7 -0
- package/src/docs/versioned/next/reference/admin-frontend.md +178 -5
- package/src/docs/versioned/next/reference/admin-spa.md +18 -1
- package/src/docs/versioned/next/reference/ai.md +2 -0
- package/src/docs/versioned/next/reference/announcements.md +179 -0
- package/src/docs/versioned/next/reference/api.md +343 -4
- package/src/docs/versioned/next/reference/comms.md +33 -0
- package/src/docs/versioned/next/reference/create-terreno-app.md +44 -0
- package/src/docs/versioned/next/reference/environment-variables.md +29 -0
- package/src/docs/versioned/next/reference/jobs.md +319 -0
- package/src/docs/versioned/next/reference/legacy/rtk.md +5 -1
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +29 -11
- package/src/docs/versioned/next/reference/mcp-server.md +2 -43
- package/src/docs/versioned/next/reference/scan-plugin.md +169 -0
- package/src/docs/versioned/next/reference/syncdb.md +38 -12
- package/src/docs/versioned/next/reference/test.md +57 -6
- package/src/docs/versioned/next/reference/ui-performance.md +2 -1
- package/src/docs/versioned/next/reference/ui.md +257 -3
- package/src/docs/versioned/next/tasks/admin-scripts-durable-jobs.md +10 -0
- package/src/docs/versioned/next/tasks/agent-ui-blocks.md +114 -0
- package/src/docs/versioned/next/tasks/announcements-surfaces.md +101 -0
- package/src/docs/versioned/next/tasks/announcements.md +173 -0
- package/src/docs/versioned/next/tasks/charts-and-dashboards.md +120 -0
- package/src/docs/versioned/next/tasks/create-terreno-app.md +82 -0
- package/src/docs/versioned/next/tasks/datatable-server-side-filtering.md +87 -0
- package/src/docs/versioned/next/tasks/examples-demo-coverage.md +123 -17
- package/src/docs/versioned/next/tasks/framework-audit-log.md +86 -0
- package/src/docs/versioned/next/tasks/gpt-chat-mascot.md +13 -0
- package/src/docs/versioned/next/tasks/job-queues.md +144 -0
- package/src/docs/versioned/next/tasks/knip-cleanup.md +146 -0
- package/src/docs/versioned/next/tasks/migrate-admin-to-syncdb.md +209 -0
- package/src/docs/versioned/next/tasks/mongodb-migrations-tooling.md +107 -0
- package/src/docs/versioned/next/tasks/notification-center.md +102 -0
- package/src/docs/versioned/next/tasks/org-management-ui.md +14 -14
- package/src/docs/versioned/next/tutorials/README.md +1 -1
- package/src/docs/versioned/next/tutorials/getting-started.md +62 -22
|
@@ -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
|
-
`
|
|
112
|
+
`rate-limiting`, `job-queues`, …) get IPs as
|
|
111
113
|
they approach the top of the queue.
|
|
112
114
|
|
|
113
115
|
## Not Included
|