Goal
Bell icon in TopBar with notification dropdown showing recent activity relevant to the current user.
Database
Create db/14_notifications.sql:
CREATE TABLE IF NOT EXISTS notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT NOT NULL,
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
type TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT,
resource_slug TEXT,
read BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_notifications_user_unread
ON notifications(user_id, read, created_at DESC);
Backend
Notification generation: In every write endpoint that creates an audit_event, also create notifications for all project members EXCEPT the actor.
Endpoints:
GET /api/notifications?unread_only=true&limit=20 — current user's notifications
POST /api/notifications/mark-read — body: {"ids": [...]} or {"all": true}
GET /api/notifications/count — returns {"unread": 5}
Real-time: Publish to Redis user:{user_id}:notifications on creation. Frontend WebSocket picks up and updates badge.
Frontend
frontend/src/components/notification-bell.tsx:
- Bell icon with unread count badge (red dot with number)
- Click opens dropdown panel (not a full page)
- List items: icon + title + relative time + unread dot
- Click notification -> navigate to relevant section/project
- "Mark all as read" link at top
- Poll
GET /api/notifications/count on mount, then update via WebSocket
Integrate into frontend/src/components/top-bar.tsx.
Acceptance Criteria
Goal
Bell icon in TopBar with notification dropdown showing recent activity relevant to the current user.
Database
Create
db/14_notifications.sql:Backend
Notification generation: In every write endpoint that creates an
audit_event, also create notifications for all project members EXCEPT the actor.Endpoints:
GET /api/notifications?unread_only=true&limit=20— current user's notificationsPOST /api/notifications/mark-read— body:{"ids": [...]}or{"all": true}GET /api/notifications/count— returns{"unread": 5}Real-time: Publish to Redis
user:{user_id}:notificationson creation. Frontend WebSocket picks up and updates badge.Frontend
frontend/src/components/notification-bell.tsx:GET /api/notifications/counton mount, then update via WebSocketIntegrate into
frontend/src/components/top-bar.tsx.Acceptance Criteria