Skip to content

ADR-0028: Event-Driven Report Pipeline with Extractable Widget

Status

Accepted

Date: 2026-03-28

Enforcement: enforced

Enforced by: pytest:tests/test_report_event_flow.py

Context

The bug report pipeline (ReportIssueLoop) silently updated TrackedReport status on disk but never notified the frontend. Users submitted bugs and saw "queued" indefinitely until the next 30-second HTTP poll. The BugReportTracker was a modal with no filtering, sorting, or inline submission — unusable for power users.

Additionally, the report UI needed to be extractable as a standalone widget for embedding in external systems (other dashboards, Slack bots, monitoring tools) without depending on the HydraFlow React context.

Decision

Backend: Publish REPORT_UPDATE events at every status transition

Added EventType.REPORT_UPDATE to the EventBus. ReportIssueLoop._do_work() now publishes events at 6 transition points: queued→in-progress, filed, retry, escalation, stale close, and fixed. The frontend reducer handles report_update WebSocket events to update trackedReports inline.

Why events over direct state mutation: The EventBus is the established notification mechanism. WebSocket subscribers receive events automatically. Metrics and logging can also react. This is consistent with how WORKER_UPDATE, MERGE_UPDATE, etc. already work.

Frontend: Self-contained useReportPoller hook + BugReportPanel component

Replaced the BugReportTracker modal with a full-width BugReportPanel dashboard tab. The panel uses useReportPoller(apiBaseUrl, reporterId, { interval }) — a hook that manages its own polling, submission, and action dispatch.

Why HTTP polling over WebSocket-only: The widget must work outside the HydraFlow dashboard where no WebSocket connection exists. HTTP polling at 10s intervals is portable — any system with HTTP access can embed it. The WebSocket report_update handler provides instant updates when available, and the poll catches up when it's not.

Why a hook, not context: useReportPoller takes apiBaseUrl and reporterId as explicit props. No HydraFlowProvider needed. This makes the component truly extractable:

<BugReportPanel apiBaseUrl="https://hydra.example.com" reporterId={userId} />

Server: Status filter on GET /api/reports

Added ?status= query parameter to GET /api/reports for server-side filtering. The useReportPoller can request only active reports, reducing payload size for embedded widgets.

Consequences

  • Report status updates are live via WebSocket when available, polled via HTTP otherwise
  • The BugReportPanel can be extracted to a standalone npm package with zero HydraFlow dependencies
  • The old BugReportTracker modal and its tests were deleted
  • REPORT_UPDATE events appear in the WebSocket stream alongside all other events — external monitoring tools can subscribe