diff --git a/docs/plans/2026-05-19-notifications-nav-group-design.md b/docs/plans/2026-05-19-notifications-nav-group-design.md new file mode 100644 index 0000000..51692b4 --- /dev/null +++ b/docs/plans/2026-05-19-notifications-nav-group-design.md @@ -0,0 +1,143 @@ +# Notifications Nav Group — Design + +**Date:** 2026-05-19 + +**Goal:** Consolidate all notification-related Central UI pages into a dedicated +**Notifications** left-menu section, split the combined Outbox page into a report +and a KPIs page, give Notification Lists a proper home, and add a per-source-site +KPI breakdown. + +## Background + +Notification-related UI is currently scattered: + +| Page | Route | Nav section | Policy | +|---|---|---|---| +| SMTP Configuration | `/admin/smtp` | Admin | RequireAdmin | +| Notification Outbox (KPI tiles **+** filterable table) | `/monitoring/notification-outbox` | Monitoring | RequireDeployment | +| Notification Lists | `/design/notification-lists/...` (form only) | none — table embedded in the External Systems page | RequireDesign | + +The Outbox page mixes KPI tiles and the filterable `Notifications`-table report on +one page. Notification Lists has no list page of its own — its table is bolted +onto `ExternalSystems.razor`. KPI infrastructure +(`NotificationKpiRequest`/`Response`, `INotificationOutboxRepository.ComputeKpisAsync`) +is global-only, despite CLAUDE.md stating KPIs are "global + per-source-site". + +## Architecture + +A new **Notifications** left-menu section consolidates these pages. Routes move +to a consistent `/notifications/*` prefix. The combined Outbox page is split into +two. Notification Lists gets a dedicated page. A bounded backend addition supplies +per-source-site KPIs. No actor topology, persistence, or message-evolution rules +change beyond the additive KPI contracts. + +## 1. Nav menu + +New `Notifications` section in `NavMenu.razor`, placed **between Deployment and +Monitoring**. Final section order: Dashboard, Admin, Design, Deployment, +Notifications, Monitoring, Audit Log. + +| Menu item | Route | Policy | +|---|---|---| +| SMTP Configuration | `/notifications/smtp` | RequireAdmin | +| Notification Lists | `/notifications/lists` | RequireDesign | +| Notification Report | `/notifications/report` | RequireDeployment | +| Notification KPIs | `/notifications/kpis` | RequireDeployment | + +Each item is wrapped in its own per-item `AuthorizeView` policy (same pattern the +Monitoring section already uses for its mixed-role items). The section header is a +plain `div` — every authenticated user holds at least one of Admin/Design/Deployment, +so the header always has ≥1 visible child and cannot be orphaned. + +SMTP Configuration is **removed** from the Admin section; Notification Outbox is +**removed** from the Monitoring section. + +## 2. SMTP Configuration + +Move `Components/Pages/Admin/SmtpConfiguration.razor` → +`Components/Pages/Notifications/SmtpConfiguration.razor`. Route `/admin/smtp` → +`/notifications/smtp`. Page content, `RequireAdmin` policy, and the +`SmtpConfiguration` namespace alias are unchanged. + +## 3. Notification Lists (new page) + +New `Components/Pages/Notifications/NotificationLists.razor` +(`/notifications/lists`, RequireDesign): a `DataTable` of notification lists with +Add and per-row Edit actions, plus an empty state — extracted verbatim from the +notification-lists block currently in `ExternalSystems.razor`. + +- `NotificationListForm.razor` routes move: + `/design/notification-lists/create` → `/notifications/lists/create`, + `/design/notification-lists/{Id:int}/edit` → `/notifications/lists/{Id:int}/edit`. + Its "Back" navigation targets `/notifications/lists`. +- The notification-lists section is **removed** from `ExternalSystems.razor`, + leaving that page purely external systems. The three `/design/notification-lists/...` + navigate-links in `ExternalSystems.razor` are removed with it. + +## 4. Notification Report + +New `Components/Pages/Notifications/NotificationReport.razor` +(`/notifications/report`, RequireDeployment), split from the existing +`Monitoring/NotificationOutbox.razor`. Retains the full filter bar, the paginated +`Notifications`-table query (`NotificationOutboxQueryRequest`), and the per-row +Retry/Discard actions. The **KPI tile row is removed** from this page. + +`Components/Pages/Monitoring/NotificationOutbox.razor` and its Monitoring nav entry +are **deleted**. + +## 5. Notification KPIs + +New `Components/Pages/Notifications/NotificationKpis.razor` +(`/notifications/kpis`, RequireDeployment) with a manual Refresh button. Two parts: + +1. **Global tiles** — the existing 5: Queue Depth, Stuck, Parked, Delivered Last + Interval, Oldest Pending Age. +2. **Per-source-site breakdown table** — one row per site with the same five + metrics, so operators can see which site is backing up. + +### Backend addition for per-site KPIs + +Bounded, additive, follows the existing global-KPI pattern: + +- `INotificationOutboxRepository.ComputePerSiteKpisAsync(...)` → returns a + per-site collection (a new `SiteNotificationKpiSnapshot` record carrying the + source site id plus the five metrics). Implemented in + `NotificationOutboxRepository`. +- New message pair in `Messages/Notification/NotificationOutboxQueries.cs`: + `PerSiteNotificationKpiRequest` / `PerSiteNotificationKpiResponse` (additive — + honors message-evolution rules). +- A handler in `NotificationOutboxActor` for the new request, mirroring the + existing `NotificationKpiRequest` handler. +- A `CommunicationService.GetPerSiteNotificationKpisAsync(...)` method mirroring + `GetNotificationKpisAsync`. + +Per CLAUDE.md, KPIs remain point-in-time computed from the `Notifications` table — +no time-series store, no historical charts (YAGNI). + +## 6. Health dashboard + +`Monitoring/Health.razor` keeps its KPI tile row unchanged. A "View details →" +link is added from that tile row to `/notifications/kpis`. + +## Error handling + +Unchanged from the current Outbox page: KPI/query faults surface as an inline +warning alert (`Success == false` → `ErrorMessage`); the site-name lookup degrades +gracefully to raw site ids. Per-site KPI faults are reported the same way. + +## Testing + +- bUnit component tests for `NotificationLists`, `NotificationReport`, + `NotificationKpis`, and the moved `SmtpConfiguration` page. +- A `NavMenu` test asserting the Notifications section renders and that per-item + visibility honors Admin/Design/Deployment roles. +- Repository tests for `ComputePerSiteKpisAsync`. +- Actor test for the `PerSiteNotificationKpiRequest` handler. +- `CommunicationService` test for `GetPerSiteNotificationKpisAsync`. + +## Out of scope + +- Historical/trend KPI charts (no time-series store). +- Any change to notification delivery, store-and-forward, or the `Notifications` + table schema. +- Renaming the Notification Outbox **component** (#21) — only the UI page names change.