# 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 --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