Files
headquarter/openspec/changes/container-monitoring-notifications/apply-pr2.md
T
alex f13a63dc2f feat: container monitoring frontend UI (PR-2)
- Custom ToastContext + ToastProvider + ToastContainer (~170 lines, no deps)
- useEvents() SSE hook with exponential backoff reconnect
- EventProvider context for app-wide SSE stream sharing
- Event-to-toast bridge with severity mapping and deduplication
- Real-time status badge updates replacing 30s polling
- EventSource auth probe (401/429 detection via fetch)
- 14 frontend tests (useEvents + toast-rules)

Quality gates: vitest 14 passed, tsc clean, eslint clean
2026-05-29 10:25:00 +02:00

126 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PR-2 Apply Report: Frontend UI for Container Monitoring & Notifications
## Status: COMPLETE
All assigned PR-2 tasks have been implemented and validated.
---
## Changed Files
### New Files (9)
| File | Purpose |
|------|---------|
| `apps/web/src/types/events.ts` | TypeScript `InstanceEventPayload` + `InstanceEventMetadata` interfaces |
| `apps/web/src/api/events.ts` | Thin EventSource wrapper + `probeEventStreamStatus` for 401/429 detection |
| `apps/web/src/hooks/use-events.ts` | `useEvents()` hook with SSE connect, exponential backoff reconnect, jitter |
| `apps/web/src/hooks/use-events.test.ts` | Unit tests for useEvents (7 tests) |
| `apps/web/src/components/toast-rules.ts` | Event-to-toast mapping + deduplication logic |
| `apps/web/src/components/toast-rules.test.ts` | Unit tests for toast rules (7 tests) |
| `apps/web/src/state/toast.tsx` | Custom lightweight toast system: ToastContext, ToastProvider, ToastContainer |
| `apps/web/src/state/events.tsx` | EventProvider context that wraps `useEvents()` and exposes events to consumers |
| `apps/web/src/components/event-toast-bridge.tsx` | Bridge component that consumes EventContext and triggers toasts via toast-rules |
### Modified Files (4)
| File | Change |
|------|--------|
| `apps/web/src/components/app-shell.tsx` | Mount `EventProvider` + `ToastProvider` + `EventToastBridge` on all authenticated routes |
| `apps/web/src/components/instance-list.tsx` | Removed 30s health polling; added SSE-driven real-time status updates; retained 60s list refresh |
| `apps/web/src/components/session-card.tsx` | Updated `statusConfig` badge colors: `starting`/`probing` → blue, `unhealthy` → amber |
| `apps/web/src/styles.css` | Added `.status-badge.starting`, `.status-badge.probing`, `.status-badge.unhealthy` + toast animation keyframes |
---
## Implementation Summary
### MON-PR2-001 / MON-PR2-002: useEvents() Hook + events.ts API Client
- `createEventSource()` returns native `EventSource` with `withCredentials: true`
- `useEvents()` hook maintains `events`, `connected`, `reconnectCount`, and `error` state
- Reconnect strategy: `delay = min(30000, 1000 * 2^attempts) * (0.8 + Math.random() * 0.4)`
- On `401` (detected via `probeEventStreamStatus` fetch probe): stops reconnecting and redirects to login
- On `429`: adds 5s penalty before next retry
- Cleans up `EventSource` and pending timeouts on unmount
### MON-PR2-003 / MON-PR2-004: Custom Toast System (No External Dependencies)
- Built a pure React + CSS toast stack:
- `ToastContext` with `addToast` / `removeToast` APIs
- `ToastProvider` manages timer-based auto-dismissal
- `ToastContainer` renders fixed-position stack with inline styles + CSS animation
- Supports severity colors: info (blue), success (green), warning (amber), error (red)
- Auto-dismiss timers: info/success 3s, warning 5s, error 10s (configurable)
- Manual dismiss via × button on each toast
### MON-PR2-005: EventProvider Context
- `EventProvider` mounts at app-shell level, calls `useEvents()` once, shares event stream via React context
- `useEventContext()` allows any descendant to subscribe to the shared SSE stream without creating duplicate connections
### MON-PR2-006: Real-Time Status Badge Updates + Polling Removal
- Removed the 30-second `checkInstanceHealth` polling loop from `instance-list.tsx`
- Added `useEffect` that listens to SSE events and updates `instances` state in-place for matching `instance_id`
- Retained a 60-second `setInterval` for `loadInstances()` as a resilience fallback
- Updated `session-card.tsx` badge color mapping to match spec:
- `starting` / `probing` → blue CSS class
- `unhealthy` → amber CSS class
- Added corresponding CSS rules in `styles.css`
### MON-PR2-007: Integration into App Shell
- `AppShell` now wraps all authenticated routes with `EventProvider` and `ToastProvider`
- `EventToastBridge` is mounted inside the providers to render toasts from SSE events
- Mobile terminal view also gets the providers (toasts still work in terminal)
### MON-PR2-008: Frontend Tests
- `use-events.test.ts`: 7 tests covering event parsing, reconnect backoff, 30s cap, 401 redirect, 429 penalty, unmount cleanup, reconnectCount exposure
- `toast-rules.test.ts`: 7 tests covering event-to-toast mapping (started, running, unhealthy, error) and deduplication within 1s window
---
## Test Commands & Exit Codes
```bash
# Focused new tests
$ cd apps/web && npx vitest run src/hooks/use-events.test.ts src/components/toast-rules.test.ts
# Exit code: 0 (14 passed)
# Broader regression check on modified page/component tests
$ cd apps/web && npx vitest run src/hooks/use-events.test.ts src/components/toast-rules.test.ts src/pages/dashboard.test.tsx src/components/terminal-session-tabs.test.tsx src/components/protected-route.test.tsx
# Exit code: 0 (25 passed)
# TypeScript type check
$ cd apps/web && npx tsc --noEmit
# Exit code: 0 (no errors)
# Lint on new/modified files only
$ cd apps/web && npx eslint <new ts/tsx files> --ext ts,tsx --report-unused-disable-directives --max-warnings 0
# Exit code: 0 (all clean)
```
> **Note:** The full `npx vitest run` shows 4 pre-existing failures in `repositories-settings-tab.test.tsx` (unrelated to this PR). The full `npx eslint` also shows 3 pre-existing errors in `terminal.tsx` and `tool-workshop.tsx`.
---
## Surprises & Decisions
1. **No sonner dependency**: The orchestrator instructed not to install `sonner` because `npm install` hangs in this environment. Implemented a custom ~170-line toast system instead using pure React + inline CSS. It supports severity, auto-dismiss, manual dismiss, and stacking with CSS animations.
2. **EventSource 401/429 detection**: Native `EventSource` does not expose HTTP status codes. Implemented a `probeEventStreamStatus()` helper that does a short `fetch()` to the SSE endpoint with `AbortController` timeout to detect 401/429 before reconnecting.
3. **Badge color CSS classes**: The existing `session-card.tsx` used raw color strings ("green", "yellow", etc.) as CSS class names, but no corresponding CSS classes existed. Added explicit `.status-badge.starting`, `.status-badge.probing`, and `.status-badge.unhealthy` rules to `styles.css`.
4. **Tunnel health removal**: The 30s polling loop in `instance-list.tsx` was the source of `healthStatus` state used for "tunnel error" badges. After removing polling, tunnel-specific health data is no longer available in real time; instances now rely on SSE `status` transitions (e.g., `unhealthy`). The tunnel error badge was removed from `instance-list.tsx` as redundant with the status badge.
5. **App.tsx vs app-shell.tsx**: This codebase has no `App.tsx`; `AppShell` in `app-shell.tsx` is the layout component that wraps all authenticated routes. Providers were mounted there instead.
---
## PR Boundary
This PR includes the complete frontend UI for container monitoring and notifications:
- SSE client hook with reconnect backoff
- Custom toast notification system
- Event provider context
- Real-time status badge updates
- Polling removal from instance list
The next PR (PR-3) should cover:
- Integration tests for lifecycle event flow
- E2E tests for container start → toast and crash detection
- Performance tuning (connection limits, queue bounds, jitter)
- Documentation updates
- Final cleanup and regression validation