feat: notification center toast coordination (PR-4)

- EventToastBridge checks notification_toast_level and notification_mute_categories
- toast-rules.ts: event-to-category/severity mapping functions
- Settings page: notification preferences section (toast level dropdown, mute checkboxes)
- Settings API types extended with notification preference fields
- 17 frontend tests (toast-rules + bridge)
- Preference hierarchy: mute categories → toast level → show/hide

Quality gates: vitest 17 passed, tsc clean, eslint clean
This commit is contained in:
2026-05-29 13:45:17 +02:00
parent ceaed9af66
commit 19242b4152
8 changed files with 808 additions and 281 deletions
@@ -195,10 +195,77 @@ cd apps/web && npx eslint src/api/notifications.ts src/state/notifications.tsx s
2. **`toBeInTheDocument` type issues in tests:** Testing-library jest-dom matchers type definitions were not automatically picked up in `.test.tsx` files. The tests run and pass at runtime; the TypeScript LSP warnings are cosmetic and do not block compilation or execution.
3. **No npm packages installed:** All frontend work was done with existing dependencies (`@phosphor-icons/react`, `react`, etc.). Relative time formatting was implemented with a 20-line custom utility rather than adding `date-fns` or similar.
## TDD Cycle Evidence (PR-4)
| Cycle | Task | Test File | RED | GREEN | Evidence |
|-------|------|-----------|-----|-------|----------|
| 1 | NC-PR4-001 (toast-rules mapping) | `src/components/toast-rules.test.ts` | 8 tests written against missing functions | Added `mapEventToCategory` + `mapEventToSeverity` | `npx vitest run src/components/toast-rules.test.ts` → 8 passed |
| 2 | NC-PR4-002 (bridge preference tests) | `src/components/event-toast-bridge.test.tsx` | 5 tests written against bridge without preference logic | Updated `EventToastBridge` with config fetch + preference checks | `npx vitest run src/components/event-toast-bridge.test.tsx` → 5 passed |
| 3 | NC-PR4-004 (edge-case tests) | `src/components/event-toast-bridge.test.tsx` | Added immediate preference change, mute override, dedup, unmapped event tests | Already green from implementation | `npx vitest run src/components/event-toast-bridge.test.tsx` → 9 passed |
| 4 | NC-PR4-005 (settings UI) | `src/pages/settings.tsx` | — | Added notification controls + `UserConfig` type extension | `npx tsc --noEmit` clean, `npx eslint` clean |
| 5 | NC-PR4-006 (REFACTOR) | All files | — | Full type check, lint, and regression test | 17 new tests pass; 25 existing tests pass; zero lint/type errors |
## Completed Tasks
### PR-4: Toast Coordination
- [x] NC-PR4-001: Extend `toast-rules.ts` with `mapEventToCategory` and `mapEventToSeverity`
- [x] NC-PR4-002: Write `EventToastBridge` preference check tests (RED)
- [x] NC-PR4-003: Update `EventToastBridge` with preference checks (GREEN)
- [x] NC-PR4-004: Bridge edge-case and integration tests (TRIANGULATE)
- [x] NC-PR4-005: Extend settings UI with notification preferences
- [x] NC-PR4-006: Final quality pass — type check, lint, regression tests (REFACTOR)
## Files Changed (PR-4)
1. `apps/web/src/components/toast-rules.ts` — Added `mapEventToCategory` and `mapEventToSeverity`
2. `apps/web/src/components/toast-rules.test.ts` *(new)* — 8 unit tests for mapping functions
3. `apps/web/src/components/event-toast-bridge.tsx` — Fetches user config, listens for `userconfig:updated`, checks preferences before showing toasts
4. `apps/web/src/components/event-toast-bridge.test.tsx` *(new)* — 9 tests for preference-based suppression, immediate updates, dedup, unmapped events
5. `apps/web/src/api/settings.ts` — Added `notification_toast_level` and `notification_mute_categories` to `UserConfig` / `UserConfigUpdate`
6. `apps/web/src/pages/settings.tsx` — Added notification preference controls (toast level select + mute category checkboxes), dispatches `userconfig:updated` on save
## Test Commands & Exit Codes (PR-4)
```bash
# Toast-rules mapping tests (8 tests)
cd apps/web && npx vitest run src/components/toast-rules.test.ts
# Exit: 0 — 8 passed
# EventToastBridge preference tests (9 tests)
cd apps/web && npx vitest run src/components/event-toast-bridge.test.tsx
# Exit: 0 — 9 passed
# All new PR-4 tests combined
cd apps/web && npx vitest run src/components/toast-rules.test.ts src/components/event-toast-bridge.test.tsx
# Exit: 0 — 17 passed
# Existing frontend tests (no regressions)
cd apps/web && npx vitest run src/hooks/use-notifications.test.tsx src/components/notification-item.test.tsx src/components/notification-center.test.tsx
# Exit: 0 — 25 passed
# Type check
cd apps/web && npx tsc --noEmit
# Exit: 0 — clean
# Lint on modified files
cd apps/web && npx eslint src/components/toast-rules.ts src/components/toast-rules.test.ts src/components/event-toast-bridge.tsx src/components/event-toast-bridge.test.tsx src/api/settings.ts src/pages/settings.tsx --ext ts,tsx
# Exit: 0 — clean
```
## Deviations from Design (PR-4)
- **No global UserConfig context:** The design assumed an existing user-config context. The frontend did not have one, so `EventToastBridge` fetches config on mount via `getUserConfig` and listens for a `userconfig:updated` `CustomEvent` dispatched by the settings page after a successful save. This achieves immediate preference updates without introducing a new provider.
## Surprises / Decisions (PR-4)
1. **Bridge processes events before config loads:** The initial `useEffect` in `EventToastBridge` could process events while `config` is still `null`. Fixed by initializing `config` to `null` and skipping the event-processing effect until config resolves. This prevents toasts from leaking before preferences are known.
2. **`UserConfig` type extended without breaking existing consumers:** Adding optional fields to `UserConfig` and `UserConfigUpdate` in `api/settings.ts` did not require changes to `sessions.tsx` or `dashboard.tsx` because they only import the API functions, not the types.
3. **Custom event for immediate updates:** Using `window.dispatchEvent(new CustomEvent("userconfig:updated", { detail: updated }))` in `settings.tsx` and listening in `event-toast-bridge.tsx` is consistent with the existing `refresh-file-tree` custom-event pattern used in `repo-workspace.tsx`.
## Remaining Tasks
- [ ] PR-4: Toast Coordination (NC-PR4-001 through NC-PR4-006)
- [x] All PR-4 tasks complete.
## PR Boundary
This progress covers PR-1, PR-2, and PR-3. PR-4 (toast coordination — EventToastBridge preferences, settings UI) is out of scope.
This progress covers PR-1, PR-2, PR-3, and PR-4. The Notification Center feature is fully implemented.