Evaluated against the current pages and their data sources. The notification outbox is central-owned, offset-paged and has its own status lifecycle. Site calls are site-owned and keyset-paged, with a different lifecycle and a Retry/Discard that relays to the owning site. A union grid would need a two-cursor merge, a status filter that does not line up, and buttons that hide different semantics. The revisit trigger (operator confusion reports) has not fired. Recommends against building it now. If confusion is reported, start with cross-links and a combined "stuck items" Health card. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
5.0 KiB
Decision: unified notifications + site-calls outbox page (ScadaBridge#44)
Date: 2026-11-09 · Decision: recommend against building a unified page now. Keep
the two report pages.
Origin: deferred-work register row 17 → Gitea #44. That row deferred M9 decision D3
(2026-06-18-m9-templates-authoring-design.md), whose revisit trigger is "operator confusion
reports".
What exists today (verified against main)
Notification Report /notifications/report |
Site Calls /site-calls/report |
|
|---|---|---|
| Owner of the queue | Central: the notification outbox is a central table, and central delivers. | Site: site store-and-forward delivers. Central's SiteCalls table is an audit mirror. |
| Query message | NotificationOutboxQueryRequest |
SiteCallQueryRequest |
| Paging | Offset (PageNumber/PageSize, TotalCount) |
Keyset (AfterCreatedAtUtc + AfterId); no total count |
| Id | string NotificationId (N-form GUID text) |
Guid TrackedOperationId |
| Status lifecycle | Pending → Retrying → Delivered / Parked / Discarded | Submitted → Forwarded → Attempted → Delivered / Parked / Failed / Discarded |
| Row provenance | list name, subject, source site and instance | channel (ApiOutbound/DbOutbound), target, HTTP status, source site; no instance column exists |
| Retry / Discard on Parked rows | Central acts on its own row. | Central relays the action to the owning site, which has its own SiteUnreachable outcome. |
Deep link ?correlationId= |
yes (Flow Debugger, Audit Log) | yes (Flow Debugger, Audit Log) |
| Role gate | RequireDeployment |
RequireDeployment |
| KPIs | Notification KPIs page plus Health-dashboard tiles | Site-call trends on the page plus Health-dashboard tiles |
The Health dashboard already shows the Notification Outbox and Site Call KPI groups next to each other. That is the existing "one place to see what is stuck".
Evaluation
A unified page would need one row model and one pager over two sources that disagree on every axis that matters to the grid:
- Paging cannot be merged cheaply. Interleaving an offset-paged source with a keyset-paged
source by
CreatedAtneeds a two-cursor merge with no total count. Alternatively, one of the two repository queries would have to be reworked. Either change touches the central query actors, not just the UI. - Status filters do not line up. The two lifecycles share only Delivered, Parked and Discarded. A unified status dropdown either hides real states (Retrying, Forwarded, Attempted, Failed) or shows options that match only one source.
- The actions have different semantics. Retrying a notification is a local central write. Retrying a site call is a relay that can fail because the site is unreachable. Putting both behind one button makes the more fragile action look the same as the safe one. A confirm dialog that has to explain this per row type is exactly the confusion the issue wants to avoid.
- The columns barely overlap. The only columns both sources share are site, status, retry count, last error and created-at. Everything an operator needs to act on is type-specific: list and subject for a notification, target and HTTP status for a site call.
- No trigger has fired. The revisit trigger is operator confusion reports. None is recorded: #44 carries no report, and no other ScadaBridge issue mentions the outbox or site-call pages (searched 2026-11-09). Both pages are discoverable from the nav rail, from the Health tiles and from Flow Debugger / Audit Log deep links.
- Cost. The existing pages are about 860 and 420 lines plus their bUnit suites
(
NotificationReportPageTests,SiteCallsReportPageTests, detail-modal and drill-in tests). A third page would duplicate filters, a detail modal and the action flows for both types. Or it would replace the two pages and require reworking their tests and deep links. That is not "small and clearly beneficial", the bar #44 set for implementing it.
Recommendation
Do not build the unified page. M9's reasoning still holds on today's code, and the trigger for revisiting it has not fired.
If operators do report confusion, start with these cheaper options before any union view:
- Cross-links: a "Site Calls →" link on the Notification Report header and the reverse. This is a one-line change per page.
- One "Stuck items" summary card on the Health dashboard. It would combine the two
existing KPI snapshots, which are already loaded there, into a single "N stuck: X
notifications, Y site calls" card that deep-links to each page with the stuck filter
pre-applied. Site Calls already accepts
?stuck=true. The Notification Report reads only?correlationId=today, so it would need the same small?stuck=seed. The card reuses the existing queries and needs no new paging model.
Revisit a true union page only if both of these prove insufficient. At that point the precondition is a shared keyset cursor on both repositories (point 1), designed first.