Skip to content

feat: error boundaries, confirmation dialog, delivery metrics API, channel name validation - #799

Merged
Abd-Standard merged 5 commits into
Core-Foundry:mainfrom
abdoolbasit374-web:feature/issue-479-482-653-654
Sep 25, 2026
Merged

Abd-Standard merged 5 commits into
Core-Foundry:mainfrom
abdoolbasit374-web:feature/issue-479-482-653-654

Conversation

@abdoolbasit374-web

Copy link
Copy Markdown
Contributor

Summary

This PR implements four issues in a single branch so they can be reviewed
and merged together.


Changes

fix(#479) — Prevent Empty Channel Names

  • SubscriptionService.subscribe() now explicitly rejects whitespace-only
    channel names (single space, tabs, newlines, mixed whitespace) with an
    INVALID_INPUT error and a clear message.
  • Extracted and exported isBlankChannelName() helper for reuse and
    independent testing.
  • 7 new unit tests covering all blank/whitespace edge cases.

feat(#482) — Notification Delivery Metrics API

  • New module listener/src/api/delivery-metrics-api.ts exposes two endpoints:
    • GET /api/notifications/delivery-metrics — live snapshot of delivery
      statistics (total, success, failure, retry, success rate, average duration,
      breakdown by type/contract/hour, error breakdown).
      Supports ?reset=true to reset the in-memory window after reading.
    • GET /api/notifications/delivery-metrics/history — paginated persisted
      snapshots from the metrics store (?limit, ?since ISO-8601 date).
  • Returns 503 when the aggregator or store is not configured.
  • Returns 400 on an invalid since date.
  • Versioned path /api/v1/notifications/delivery-metrics works via the
    existing rewrite middleware.
  • Wired into events-server.ts before the 404 fallback.
  • 12 integration test cases covering snapshot content, 503 fallback,
    reset behaviour, history pagination, invalid-date validation, empty store,
    and the versioned path.

feat(#653) — Reusable Confirmation Dialog Component

  • New ConfirmationDialog component built on the alertdialog ARIA pattern:
    • role="alertdialog", aria-modal, aria-labelledby, aria-describedby
    • Confirm button auto-focused on open (most dialogs are triggered
      intentionally — one Enter press confirms).
    • Full focus trap: Tab / Shift+Tab cycle stays within the dialog only.
    • Escape key and backdrop click both fire onCancel.
    • Focus restored to the trigger element on close.
    • danger / primary variant prop for the confirm button.
    • Custom confirmLabel / cancelLabel props.
    • Existing UI components can reuse it without duplicating any dialog logic.
  • 20 test cases: rendering, interactions, keyboard focus trap, focus
    restoration, and a full axe accessibility audit.

feat(#654) — Component-Level Error Boundaries

  • New ErrorBoundary React class component:
    • Catches render-time and lifecycle errors in its subtree via
      getDerivedStateFromError + componentDidCatch.
    • Logs error.name, error.message, and the React componentStack —
      deliberately omits raw JS stack frames to avoid leaking sensitive
      variable values.
    • Optional onError callback for external error-tracking services
      (exceptions thrown by the callback are swallowed).
    • Optional custom fallback prop; defaults to a branded fallback UI
      with section name and a "Try again" reset button.
    • role="alert" + aria-live="assertive" on the fallback so screen
      readers announce the error.
  • App.tsx: all 11 dashboard panels (Event Explorer, Delivery Timeline,
    Activity Feed, Webhook Performance, Export History, Notification Search,
    Preferences, Templates, Channel Details, User Activity, Retry Statistics)
    are individually wrapped in named ErrorBoundary instances — a failure
    in one panel leaves every other panel fully functional.
  • 19 test cases: pass-through rendering, fallback UI, custom fallback,
    retry/reset, onError callback, safe logging, sibling isolation, nested
    boundary isolation, and role="alert" accessibility.

Testing

All new behaviour is covered by tests co-located with the source files:

File Tests
subscription-service.test.ts +11 cases (whitespace validation)
delivery-metrics-api.test.ts 12 cases (new file)
ConfirmationDialog.test.tsx 20 cases (new file)
ErrorBoundary.test.tsx 19 cases (new file)

CI runs lint → typecheck → tests for both dashboard and listener on
every PR — all checks are expected to pass.


Closes

Closes #479
Closes #482
Closes #653
Closes #654

Harbduls and others added 5 commits September 25, 2026 12:00
- Export isBlankChannelName() helper from subscription-service
- subscribe() returns INVALID_INPUT error for whitespace-only channel names
- Add edge-case unit tests: single space, tabs, newline, mixed whitespace
- Add isBlankChannelName() unit test suite (7 cases)

Closes Core-Foundry#479
- New module: listener/src/api/delivery-metrics-api.ts
  - GET /api/notifications/delivery-metrics  — live snapshot from aggregator
  - GET /api/notifications/delivery-metrics/history — persisted history from store
  - ?reset=true resets the in-memory aggregator window after read
  - ?limit and ?since query params for history pagination
  - 503 when aggregator/store not configured, 400 on invalid date
- Wire handleDeliveryMetricsRequest() into events-server.ts
- Tests: delivery-metrics-api.test.ts (12 test cases)
  - snapshot content, 503 fallback, reset behaviour, history pagination,
    invalid-since validation, empty store, versioned path (/api/v1/...)

Closes Core-Foundry#482
- ConfirmationDialog.tsx wraps alertdialog pattern with full a11y support
  - role=alertdialog, aria-modal, aria-labelledby, aria-describedby
  - Confirm button auto-focused on open (primary action intent)
  - Focus trap: Tab/Shift+Tab cycles within dialog only
  - Escape key and backdrop click both fire onCancel
  - Focus restored to trigger element on close
  - danger/primary variant prop for confirm button styling
  - Custom confirmLabel / cancelLabel props
- ConfirmationDialog.test.tsx: 20 test cases covering
  - rendering (open/closed, labels, variants)
  - interactions (confirm, cancel, backdrop, escape)
  - keyboard focus trap (Tab wrap, Shift+Tab wrap)
  - focus restoration on close
  - axe accessibility audit (alertdialog, aria attributes)

Closes Core-Foundry#653
…oard

- New ErrorBoundary class component (ErrorBoundary.tsx)
  - getDerivedStateFromError catches render-time errors in subtree
  - componentDidCatch logs error.name + error.message + componentStack
    (no raw JS stack frames — avoids leaking sensitive variable values)
  - optional onError() callback for external error tracking
  - optional custom fallback prop; defaults to branded fallback UI
  - 'Try again' button resets boundary state for retry
  - role=alert + aria-live=assertive on fallback for screen readers
  - section prop names the boundary in logs and fallback heading

- App.tsx: wrap all 11 dashboard panels in ErrorBoundary
  - Each panel has its own named boundary (Event Explorer, Delivery
    Timeline, Activity Feed, Webhook Performance, etc.)
  - A failure in one panel leaves all other panels fully functional

- ErrorBoundary.test.tsx: 19 test cases covering
  - pass-through rendering when no error
  - fallback UI content and section name
  - custom fallback prop
  - retry button resets and re-renders children
  - onError callback receives error + componentStack
  - onError exceptions are swallowed
  - console.error logs name+message but not raw stack frames
  - sibling boundary isolation (one error does not affect sibling)
  - nested boundary isolation (inner caught, outer unaffected)
  - role=alert accessibility

Closes Core-Foundry#654
@drips-wave

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

@abdoolbasit374-web Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants