2bec205a30
- Bell icon in icon registry (Phosphor Bell) - NotificationProvider context with polling (15s unread / 30s list) - useNotifications() hook with optimistic updates - NotificationCenter component: bell + badge + dropdown panel - NotificationItem component: severity icon, title, relative time, actions - AppShell integration: mount in header-actions, hidden on mobile - CSS styles: dropdown, items, unread/read states, empty state - formatRelativeTime utility (custom, no new deps) - 25 frontend tests (9 hook + 6 item + 10 center) Quality gates: vitest 25 passed, tsc clean, eslint clean
14 KiB
14 KiB
Apply Progress: Notification Center
TDD Cycle Evidence (PR-1)
| Cycle | Task | Test File | RED | GREEN | Evidence |
|---|---|---|---|---|---|
| 1 | NC-PR1-004 (basic CRUD) | tests/unit/test_notification_service.py |
13 tests written against missing service | All 13 pass | pytest tests/unit/test_notification_service.py → 13 passed |
| 2 | NC-PR1-005 (edge cases) | tests/unit/test_notification_service.py |
Already included in cycle 1 | Added wrong-owner, mute-categories, cross-user isolation | Same 13 tests pass |
| 3 | NC-PR1-007 (basic endpoints) | tests/integration/test_notifications_api.py |
10 tests written against missing router | All 10 pass | pytest tests/integration/test_notifications_api.py → 10 passed |
| 4 | NC-PR1-008 (API edge cases) | tests/integration/test_notifications_api.py |
Already included in cycle 3 | Pagination, 404 ownership, mute categories at API layer | Same 10 tests pass |
| 5 | NC-PR1-010 (REFACTOR) | All files | — | ruff clean, no regressions | ruff check passes on all new files; existing unit tests 223 passed (4 pre-existing failures unrelated) |
TDD Cycle Evidence (PR-2)
| Cycle | Task | Test File | RED | GREEN | Evidence |
|---|---|---|---|---|---|
| 1 | NC-PR2-004 (producer flow) | tests/integration/test_notifications_lifecycle.py |
4 tests written against unwired producers | Wired lifecycle_hooks.py and health_monitor.py |
4 passed |
| 2 | NC-PR2-004 (severity mapping) | tests/integration/test_notifications_lifecycle.py |
Added error + warning severity tests | Already green from implementation | 6 passed |
| 3 | NC-PR2-003 (UserConfig schema) | src/api/user_config.py |
Schema extended with new optional fields | PATCH/GET endpoints validate correctly | Verified manually |
| 4 | NC-PR2-005 (REFACTOR) | All files | — | ruff clean, no regressions across 41 related tests | All pass |
TDD Cycle Evidence (PR-3)
| Cycle | Task | Test File | RED | GREEN | Evidence |
|---|---|---|---|---|---|
| 1 | NC-PR3-009 (hook tests) | src/hooks/use-notifications.test.tsx |
9 tests written against stub provider/hook | Implemented NotificationProvider + useNotifications |
9 passed |
| 2 | NC-PR3-008 (component tests) | src/components/notification-center.test.tsx |
10 tests written against stub component | Implemented NotificationCenter + NotificationItem |
10 passed |
| 3 | NC-PR3-005 (NotificationItem tests) | src/components/notification-item.test.tsx |
6 tests written against stub component | Implemented NotificationItem |
6 passed |
| 4 | NC-PR3-012 (REFACTOR) | All files | — | tsc --noEmit clean, eslint clean |
Zero errors |
Completed Tasks
PR-1: Backend Core
- NC-PR1-001: Alembic migration for
notificationstable - NC-PR1-002: SQLAlchemy
Notificationmodel (apps/api/src/models/notification.py) - NC-PR1-003: Export
Notificationinmodels/__init__.py - NC-PR1-004: NotificationService unit tests — basic CRUD (RED)
- NC-PR1-005: Implement
NotificationServicesingleton (GREEN) - NC-PR1-006: Service edge-case and isolation tests (TRIANGULATE)
- NC-PR1-007: API integration tests — basic endpoints (RED)
- NC-PR1-008: Implement FastAPI router + Pydantic schemas (GREEN)
- NC-PR1-009: API edge-case and ownership tests (TRIANGULATE)
- NC-PR1-010: Register router in
main.py+ importNotificationfor Alembic - NC-PR1-011: Code quality pass — ruff, test regressions, smoke tests (REFACTOR)
PR-2: Backend Integration
- NC-PR2-001: Wire
lifecycle_hooks.pytoNotificationService - NC-PR2-002: Wire
health_monitor.pytoNotificationService - NC-PR2-003: Extend
UserConfigschema for notification preferences - NC-PR2-004: Event producer integration tests (RED)
- NC-PR2-005: Verify producer tests pass and clean up (GREEN / REFACTOR)
PR-3: Frontend Core
- NC-PR3-001: Add "bell" icon to icon registry (
apps/web/src/utils/icons.ts,apps/web/src/components/icon.tsx) - NC-PR3-002:
NotificationProvidercontext with polling (apps/web/src/state/notifications.tsx) - NC-PR3-003:
useNotifications()hook (apps/web/src/hooks/use-notifications.ts) - NC-PR3-004:
NotificationCentercomponent — bell + dropdown panel (apps/web/src/components/notification-center.tsx) - NC-PR3-005:
NotificationItemcomponent — single row (apps/web/src/components/notification-item.tsx) - NC-PR3-006: AppShell integration — mount
NotificationCenterin header-actions (apps/web/src/components/app-shell.tsx) - NC-PR3-007: CSS styles for notification center (
apps/web/src/styles.css) - NC-PR3-008: Component tests for
NotificationCenter(apps/web/src/components/notification-center.test.tsx) - NC-PR3-009: Hook tests for
useNotifications(apps/web/src/hooks/use-notifications.test.tsx)
Files Changed
PR-1 Files
apps/api/alembic/versions/2026_05_29_add_notifications_table.py(new) — Alembic migrationapps/api/src/models/notification.py(new) — SQLAlchemy modelapps/api/src/models/__init__.py— ExportNotificationapps/api/src/services/notification_service.py(new) —NotificationServicesingletonapps/api/src/api/notifications.py(new) — FastAPI router + Pydantic schemasapps/api/src/api/__init__.py— Exportnotifications_routerapps/api/src/main.py— Register router, importNotificationfor Alembicapps/api/tests/unit/test_notification_service.py(new) — 13 unit testsapps/api/tests/integration/test_notifications_api.py(new) — 10 integration testsapps/api/tests/integration/test_models.py— Updated expected tables list
PR-2 Files
apps/api/src/services/lifecycle_hooks.py— WiredNotificationServiceafter event bus publishapps/api/src/services/health_monitor.py— WiredNotificationServiceafter state change event publishapps/api/src/api/user_config.py— Addednotification_mute_categoriesandnotification_toast_levelto Pydantic schemasapps/api/tests/integration/test_notifications_lifecycle.py(new) — 6 integration tests for event-to-notification flow
PR-3 Files
apps/web/src/api/notifications.ts(new) — API client for notification endpointsapps/web/src/utils/icons.ts— Added"bell"toIconNameunion andiconRegistryapps/web/src/components/icon.tsx— AddedBellimport and mappingapps/web/src/utils/time.ts(new) —formatRelativeTimeutilityapps/web/src/state/notifications.tsx(new) —NotificationProviderwith polling, optimistic mutations, visibility pauseapps/web/src/hooks/use-notifications.ts(new) —useNotificationsconsumer hookapps/web/src/hooks/use-notifications.test.tsx(new) — 9 hook tests (RED → GREEN)apps/web/src/components/notification-item.tsx(new) — Presentational notification rowapps/web/src/components/notification-item.test.tsx(new) — 6 component tests (RED → GREEN)apps/web/src/components/notification-center.tsx(new) — Bell icon, badge, dropdown panelapps/web/src/components/notification-center.test.tsx(new) — 10 component tests (RED → GREEN)apps/web/src/styles.css— Added notification center + item + dropdown CSS utilitiesapps/web/src/components/app-shell.tsx— MountedNotificationProviderandNotificationCenterin header-actions
Test Commands & Exit Codes
PR-1
# Unit tests for NotificationService (13 tests)
cd apps/api && python -m pytest tests/unit/test_notification_service.py -v
# Exit: 0 — 13 passed
# Integration tests for notifications API (10 tests)
cd apps/api && python -m pytest tests/integration/test_notifications_api.py -v
# Exit: 0 — 10 passed
# Existing unit tests (no regressions in our code)
cd apps/api && python -m pytest tests/unit/ -v
# Exit: 1 — 223 passed, 4 failed (pre-existing failures in test_config.py and test_git_repository_clone_preflight.py)
PR-2
# New integration tests for event producers (6 tests)
cd apps/api && python -m pytest tests/integration/test_notifications_lifecycle.py -v
# Exit: 0 — 6 passed
# Combined relevant test suite (41 tests)
cd apps/api && python -m pytest \
tests/unit/test_notification_service.py \
tests/integration/test_notifications_api.py \
tests/integration/test_notifications_lifecycle.py \
tests/unit/test_health_monitor.py \
tests/integration/test_events.py \
-v
# Exit: 0 — 41 passed
# Ruff linting on all PR-2 modified files
cd apps/api && python -m ruff check \
src/services/lifecycle_hooks.py \
src/services/health_monitor.py \
src/api/user_config.py \
tests/integration/test_notifications_lifecycle.py
# Exit: 0 — All checks passed
PR-3
# Hook tests (9 tests)
cd apps/web && npx vitest run src/hooks/use-notifications.test.tsx
# Exit: 0 — 9 passed
# NotificationItem tests (6 tests)
cd apps/web && npx vitest run src/components/notification-item.test.tsx
# Exit: 0 — 6 passed
# NotificationCenter tests (10 tests)
cd apps/web && npx vitest run src/components/notification-center.test.tsx
# Exit: 0 — 10 passed
# All new frontend tests combined (25 tests)
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 new/modified files
cd apps/web && npx eslint src/api/notifications.ts src/state/notifications.tsx src/hooks/use-notifications.ts src/components/notification-item.tsx src/components/notification-center.tsx src/components/app-shell.tsx src/utils/icons.ts src/components/icon.tsx src/utils/time.ts src/hooks/use-notifications.test.tsx src/components/notification-item.test.tsx src/components/notification-center.test.tsx --ext ts,tsx
# Exit: 0 — clean
Deviations from Design
PR-1
- SQLAlchemy
metadatacolumn name conflict:Base.metadatais reserved by SQLAlchemy DeclarativeBase. Usednotification_metadataas the Python attribute name with DB column name"metadata". In the Pydantic response model, usedField(serialization_alias="metadata")so the JSON API still exposesmetadataas specified. created_attype in Pydantic: Useddatetimeinstead ofstrto leverage FastAPI's automatic ISO serialization.
PR-2
- None. All mappings and behaviors match the design spec (section 1.3) and task requirements exactly.
PR-3
- Polling interval management: The provider uses two
useEffecthooks plusstartPolling/stopPollinghelpers. A race condition between the dropdown effect and the initial start effect in React Strict Mode was discovered and fixed by requiringunreadIntervalRef.currentto be truthy before the dropdown effect resumes list polling. This ensuresstartPollingalways owns initial list fetch. formatRelativeTimeutility: Design did not specify a relative-time formatter. Added a lightweight custom utility (apps/web/src/utils/time.ts) rather than installing a date library, per the constraint not to add npm packages.
Surprises / Decisions
PR-1
- SQLite
func.now()resolution:test_list_notifications_orders_by_created_at_descfailed because multiple rapid INSERTs got identical timestamps. Fixed by explicitly settingcreated_atoffsets in the test after creation. - Pre-existing integration test failures: ~40 integration tests fail due to missing
asyncpgmodule and direct PostgreSQL connection attempts in their custom setup code. These are unrelated to our changes. - Pre-existing
test_models.pyoutdated: Thetest_expected_tables_are_registeredassertion had a hardcoded set missing many newer tables. Updated it to include all current tables (includingnotifications).
PR-2
- Health monitor
test_health_monitor_unhealthy_creates_warning_notificationrequiredpublic_url: The health monitor only checks tunnel health wheninstance.public_urlis truthy. Without setting it on the test fixture instance,_derive_statusreturned"running"instead of"unhealthy", which created an"info"notification. Fixed by settingtest_instance.public_urlin the test before calling_check_instance. - Patch target for failure test: The
test_notification_failure_does_not_block_event_pipelinepatchessrc.services.lifecycle_hooks.notification_service.create_notification. This only works becauselifecycle_hooks.pyimportsnotification_serviceat module level, making the attribute resolvable byunittest.mock.patch. - No schema migration needed for UserConfig: Preferences are stored in the existing JSON
configblob, consistent with the existing pattern (theme, editor, git identity). No Alembic migration required.
PR-3
- React Strict Mode interval race: In
NotificationProvider, the dropdownuseEffectwas setting the list poll interval before the initial-startuseEffectcalledstartPolling, which causedstartPollingto skip its initialfetchList()call. Fixed by adding&& unreadIntervalRef.currentto the dropdown effect's resume branch, so it only resumes an already-active polling session. toBeInTheDocumenttype issues in tests: Testing-library jest-dom matchers type definitions were not automatically picked up in.test.tsxfiles. The tests run and pass at runtime; the TypeScript LSP warnings are cosmetic and do not block compilation or execution.- 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 addingdate-fnsor similar.
Remaining Tasks
- PR-4: Toast Coordination (NC-PR4-001 through NC-PR4-006)
PR Boundary
This progress covers PR-1, PR-2, and PR-3. PR-4 (toast coordination — EventToastBridge preferences, settings UI) is out of scope.